Building a CNN with claude for CNN final deep learning¶

imports and GPU check¶

In [4]:
!ls
augmented.keras  model_best.keras   residual.keras		    test
batch.keras	 model.png	    res-net.keras		    train
model_best.h5	 regularized.keras  res-net-trainable-layers.keras  validation
In [6]:
%cd ../
/content/drive/MyDrive/data

Environment Setup¶

In [1]:
import os
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
from tensorflow.keras.applications import VGG16, ResNet50, InceptionV3
import pandas as pd
from sklearn.metrics import confusion_matrix, classification_report

# Check for GPU availability
print("GPU Available: ", tf.config.list_physical_devices('GPU'))
print("TensorFlow version: ", tf.__version__)

# Set random seeds for reproducibility
np.random.seed(42)
tf.random.set_seed(42)
GPU Available:  [PhysicalDevice(name='/physical_device:GPU:0', device_type='GPU')]
TensorFlow version:  2.18.0

Dataset Exploration¶

In [7]:
# Define the base directory
base_dir = "split_clash"

# Display the directory structure
print("Dataset structure:")
for split in ['train', 'validation', 'test']:
    split_dir = os.path.join(base_dir, split)
    print(f"\n{split} directory:")
    classes = os.listdir(split_dir)
    print(f"Classes: {classes}")

    for cls in classes:
        class_dir = os.path.join(split_dir, cls)
        num_images = len(os.listdir(class_dir))
        print(f"  - {cls}: {num_images} images")

# Count total images
total_images = 0
for split in ['train', 'validation', 'test']:
    split_dir = os.path.join(base_dir, split)
    for cls in os.listdir(split_dir):
        class_dir = os.path.join(split_dir, cls)
        total_images += len(os.listdir(class_dir))

print(f"\nTotal images in dataset: {total_images}")
Dataset structure:

train directory:
Classes: ['th_13', 'th_14', 'th_11', 'th_17', 'th_15', 'th_12', 'th_16', 'th_10']
  - th_13: 90 images
  - th_14: 90 images
  - th_11: 90 images
  - th_17: 93 images
  - th_15: 98 images
  - th_12: 90 images
  - th_16: 81 images
  - th_10: 90 images

validation directory:
Classes: ['th_13', 'th_15', 'th_14', 'th_12', 'th_17', 'th_10', 'th_11', 'th_16']
  - th_13: 20 images
  - th_15: 22 images
  - th_14: 20 images
  - th_12: 20 images
  - th_17: 20 images
  - th_10: 20 images
  - th_11: 20 images
  - th_16: 18 images

test directory:
Classes: ['th_16', 'th_12', 'th_15', 'th_14', 'th_17', 'th_11', 'th_13', 'th_10']
  - th_16: 18 images
  - th_12: 20 images
  - th_15: 22 images
  - th_14: 20 images
  - th_17: 21 images
  - th_11: 20 images
  - th_13: 20 images
  - th_10: 20 images

Total images in dataset: 1043

Sample Image Visualization¶

In [8]:
import matplotlib.pyplot as plt
import random
from tensorflow.keras.preprocessing.image import load_img, img_to_array

# Function to load and display sample images from each class
def display_sample_images(base_dir, split='train', num_samples=2):
    split_dir = os.path.join(base_dir, split)
    classes = sorted(os.listdir(split_dir))

    # Set up the plot grid
    fig, axes = plt.subplots(len(classes), num_samples, figsize=(12, 3*len(classes)))

    for i, cls in enumerate(classes):
        class_dir = os.path.join(split_dir, cls)
        image_files = os.listdir(class_dir)

        # Select random samples
        samples = random.sample(image_files, min(num_samples, len(image_files)))

        for j, sample in enumerate(samples):
            img_path = os.path.join(class_dir, sample)
            img = load_img(img_path)

            # Get image dimensions
            width, height = img.size

            # Display image
            axes[i, j].imshow(img)
            axes[i, j].set_title(f"{cls} ({width}x{height})")
            axes[i, j].axis('off')

    plt.tight_layout()
    plt.show()

    # Return a sample image to determine typical dimensions
    sample_img_path = os.path.join(split_dir, classes[0], os.listdir(os.path.join(split_dir, classes[0]))[0])
    sample_img = load_img(sample_img_path)
    print(f"Sample image dimensions: {sample_img.size}")
    return sample_img.size

# Visualize samples from training set
img_size = display_sample_images(base_dir, 'train', 3)
No description has been provided for this image
Sample image dimensions: (2532, 1170)

Class distribution analysis¶

In [9]:
# Analyze class distribution
def analyze_class_distribution(base_dir):
    # Initialize dictionaries to store counts
    distribution = {split: {} for split in ['train', 'validation', 'test']}

    # Count images per class in each split
    for split in ['train', 'validation', 'test']:
        split_dir = os.path.join(base_dir, split)
        classes = sorted(os.listdir(split_dir))

        for cls in classes:
            class_dir = os.path.join(split_dir, cls)
            distribution[split][cls] = len(os.listdir(class_dir))

    # Create a DataFrame for easier visualization
    df_dist = pd.DataFrame(distribution)

    # Calculate total per class
    df_dist['total'] = df_dist.sum(axis=1)

    # Calculate percentage per split
    for split in ['train', 'validation', 'test']:
        df_dist[f'{split}_pct'] = (df_dist[split] / df_dist['total'] * 100).round(2)

    # Display table
    print("Class Distribution:")
    print(df_dist)

    # Plot distribution
    plt.figure(figsize=(12, 6))

    # Plot count per class per split
    ax1 = plt.subplot(1, 2, 1)
    df_dist[['train', 'validation', 'test']].plot(kind='bar', ax=ax1)
    plt.title('Images per Class')
    plt.ylabel('Number of Images')
    plt.xlabel('Town Hall Level')
    plt.legend(title='Split')

    # Plot total distribution
    ax2 = plt.subplot(1, 2, 2)
    df_dist['total'].plot(kind='bar', ax=ax2, color='teal')
    plt.title('Total Images per Class')
    plt.ylabel('Number of Images')
    plt.xlabel('Town Hall Level')

    plt.tight_layout()
    plt.show()

    return df_dist

# Run the analysis
class_dist = analyze_class_distribution(base_dir)
Class Distribution:
       train  validation  test  total  train_pct  validation_pct  test_pct
th_10     90          20    20    130      69.23           15.38     15.38
th_11     90          20    20    130      69.23           15.38     15.38
th_12     90          20    20    130      69.23           15.38     15.38
th_13     90          20    20    130      69.23           15.38     15.38
th_14     90          20    20    130      69.23           15.38     15.38
th_15     98          22    22    142      69.01           15.49     15.49
th_16     81          18    18    117      69.23           15.38     15.38
th_17     93          20    21    134      69.40           14.93     15.67
No description has been provided for this image

Image preprocessing¶

In [10]:
# After analyzing the sample images, define appropriate dimensions
# Note: We'll finalize this once we see the actual image dimensions

def determine_optimal_dimensions(base_dir):
    """
    Analyze a sample of images to determine optimal dimensions for preprocessing
    """
    dimensions = []

    # Sample some images from each class in the training set
    train_dir = os.path.join(base_dir, 'train')
    classes = os.listdir(train_dir)

    for cls in classes:
        class_dir = os.path.join(train_dir, cls)
        images = os.listdir(class_dir)[:5]  # Sample up to 5 images per class

        for img_name in images:
            img_path = os.path.join(class_dir, img_name)
            img = load_img(img_path)
            dimensions.append(img.size)

    # Calculate statistics
    widths = [d[0] for d in dimensions]
    heights = [d[1] for d in dimensions]

    print(f"Width - Min: {min(widths)}, Max: {max(widths)}, Median: {np.median(widths)}")
    print(f"Height - Min: {min(heights)}, Max: {max(heights)}, Median: {np.median(heights)}")

    # Plot dimension distribution
    plt.figure(figsize=(12, 5))

    plt.subplot(1, 2, 1)
    plt.hist(widths, bins=10)
    plt.title('Width Distribution')
    plt.xlabel('Width (pixels)')

    plt.subplot(1, 2, 2)
    plt.hist(heights, bins=10)
    plt.title('Height Distribution')
    plt.xlabel('Height (pixels)')

    plt.tight_layout()
    plt.show()

    # Suggest dimensions based on the median (rounded to nearest multiple of 32)
    median_width = np.median(widths)
    median_height = np.median(heights)

    suggested_width = int(np.ceil(median_width / 32) * 32)
    suggested_height = int(np.ceil(median_height / 32) * 32)

    # If too large, cap at reasonable size
    max_dim = 224  # Standard input size for many pre-trained models

    if suggested_width > max_dim or suggested_height > max_dim:
        aspect_ratio = median_width / median_height
        if aspect_ratio > 1:
            suggested_width = max_dim
            suggested_height = int(max_dim / aspect_ratio)
        else:
            suggested_height = max_dim
            suggested_width = int(max_dim * aspect_ratio)

    print(f"Suggested dimensions: {suggested_width}x{suggested_height}")
    return suggested_width, suggested_height

# Run the analysis to determine optimal dimensions
target_width, target_height = determine_optimal_dimensions(base_dir)

# We'll use these dimensions throughout our project
IMG_SIZE = (target_width, target_height)
print(f"Selected image dimensions: {IMG_SIZE}")
Width - Min: 2436, Max: 2622, Median: 2532.0
Height - Min: 1125, Max: 1206, Median: 1170.0
No description has been provided for this image
Suggested dimensions: 224x103
Selected image dimensions: (224, 103)

Data Generators Setup¶

In [11]:
# Define image dimensions and batch size
IMG_WIDTH, IMG_HEIGHT = 224, 103
BATCH_SIZE = 32

# Define data generators
def create_data_generators(base_dir):
    # Create basic data generators (no augmentation yet)
    train_datagen = ImageDataGenerator(
        rescale=1./255
    )

    validation_datagen = ImageDataGenerator(
        rescale=1./255
    )

    test_datagen = ImageDataGenerator(
        rescale=1./255
    )

    # Create generators for each split
    train_generator = train_datagen.flow_from_directory(
        os.path.join(base_dir, 'train'),
        target_size=(IMG_HEIGHT, IMG_WIDTH),
        batch_size=BATCH_SIZE,
        class_mode='categorical',
        shuffle=True
    )

    validation_generator = validation_datagen.flow_from_directory(
        os.path.join(base_dir, 'validation'),
        target_size=(IMG_HEIGHT, IMG_WIDTH),
        batch_size=BATCH_SIZE,
        class_mode='categorical',
        shuffle=False
    )

    test_generator = test_datagen.flow_from_directory(
        os.path.join(base_dir, 'test'),
        target_size=(IMG_HEIGHT, IMG_WIDTH),
        batch_size=BATCH_SIZE,
        class_mode='categorical',
        shuffle=False
    )

    # Get class indices to understand the mapping
    class_indices = train_generator.class_indices
    print("Class indices:", class_indices)

    # Number of classes
    num_classes = len(class_indices)
    print(f"Number of classes: {num_classes}")

    return train_generator, validation_generator, test_generator, num_classes

# Create generators
base_dir = "split_clash"  # Using the correct directory name
train_generator, validation_generator, test_generator, num_classes = create_data_generators(base_dir)

# Calculate steps per epoch
steps_per_epoch = train_generator.samples // BATCH_SIZE
validation_steps = validation_generator.samples // BATCH_SIZE + 1  # Add 1 to ensure all samples are evaluated
print(f"Steps per epoch: {steps_per_epoch}")
print(f"Validation steps: {validation_steps}")

# Extract class names for later use
class_names = list(train_generator.class_indices.keys())
print(f"Class names: {class_names}")
Found 721 images belonging to 8 classes.
Found 160 images belonging to 8 classes.
Found 161 images belonging to 8 classes.
Class indices: {'th_10': 0, 'th_11': 1, 'th_12': 2, 'th_13': 3, 'th_14': 4, 'th_15': 5, 'th_16': 6, 'th_17': 7}
Number of classes: 8
Steps per epoch: 22
Validation steps: 6
Class names: ['th_10', 'th_11', 'th_12', 'th_13', 'th_14', 'th_15', 'th_16', 'th_17']

Baseline CNN Arch¶

In [12]:
def create_baseline_model(input_shape, num_classes):
    """
    Create a baseline CNN model for the Town Hall classification task
    """
    model = keras.Sequential([
        # First convolutional block
        layers.Conv2D(32, (3, 3), activation='relu', padding='same', input_shape=input_shape),
        layers.MaxPooling2D((2, 2)),

        # Second convolutional block
        layers.Conv2D(64, (3, 3), activation='relu', padding='same'),
        layers.MaxPooling2D((2, 2)),

        # Third convolutional block
        layers.Conv2D(128, (3, 3), activation='relu', padding='same'),
        layers.MaxPooling2D((2, 2)),

        # Fourth convolutional block
        layers.Conv2D(256, (3, 3), activation='relu', padding='same'),
        layers.MaxPooling2D((2, 2)),

        # Flatten and dense layers
        layers.Flatten(),
        layers.Dense(512, activation='relu'),
        layers.Dense(num_classes, activation='softmax')
    ])

    # Compile the model
    model.compile(
        optimizer='adam',
        loss='categorical_crossentropy',
        metrics=['accuracy']
    )

    return model

# Create the baseline model
input_shape = (IMG_HEIGHT, IMG_WIDTH, 3)  # Height, Width, Channels
baseline_model = create_baseline_model(input_shape, num_classes)

# Print model summary
baseline_model.summary()

# Visualize the model architecture (optional)
from tensorflow.keras.utils import plot_model
plot_model(baseline_model, to_file='baseline_model.png', show_shapes=True, show_layer_names=True)
/usr/local/lib/python3.11/dist-packages/keras/src/layers/convolutional/base_conv.py:107: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(activity_regularizer=activity_regularizer, **kwargs)
Model: "sequential"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ conv2d (Conv2D)                 │ (None, 103, 224, 32)   │           896 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d (MaxPooling2D)    │ (None, 51, 112, 32)    │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_1 (Conv2D)               │ (None, 51, 112, 64)    │        18,496 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_1 (MaxPooling2D)  │ (None, 25, 56, 64)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_2 (Conv2D)               │ (None, 25, 56, 128)    │        73,856 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_2 (MaxPooling2D)  │ (None, 12, 28, 128)    │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_3 (Conv2D)               │ (None, 12, 28, 256)    │       295,168 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_3 (MaxPooling2D)  │ (None, 6, 14, 256)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ flatten (Flatten)               │ (None, 21504)          │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense (Dense)                   │ (None, 512)            │    11,010,560 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_1 (Dense)                 │ (None, 8)              │         4,104 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
 Total params: 11,403,080 (43.50 MB)
 Trainable params: 11,403,080 (43.50 MB)
 Non-trainable params: 0 (0.00 B)
Out[12]:
No description has been provided for this image

Train Baseline Model¶

In [13]:
# Create a directory for model checkpoints
checkpoint_dir = 'model_checkpoints'
os.makedirs(checkpoint_dir, exist_ok=True)

# Setup callbacks
def setup_callbacks(model_name):
    early_stopping = EarlyStopping(
        monitor='val_accuracy',
        patience=10,
        restore_best_weights=True,
        verbose=1
    )

    checkpoint_path = os.path.join(checkpoint_dir, f"{model_name}_best.h5")
    model_checkpoint = ModelCheckpoint(
        checkpoint_path,
        monitor='val_accuracy',
        save_best_only=True,
        mode='max',
        verbose=1
    )

    return [early_stopping, model_checkpoint]

# Function to plot learning curves
def plot_learning_curves(history, model_name):
    plt.figure(figsize=(12, 5))

    # Plot training & validation accuracy
    plt.subplot(1, 2, 1)
    plt.plot(history.history['accuracy'])
    plt.plot(history.history['val_accuracy'])
    plt.title(f'{model_name} - Accuracy')
    plt.ylabel('Accuracy')
    plt.xlabel('Epoch')
    plt.legend(['Train', 'Validation'], loc='lower right')

    # Plot training & validation loss
    plt.subplot(1, 2, 2)
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.title(f'{model_name} - Loss')
    plt.ylabel('Loss')
    plt.xlabel('Epoch')
    plt.legend(['Train', 'Validation'], loc='upper right')

    plt.tight_layout()
    plt.savefig(f'{model_name}_learning_curves.png')
    plt.show()

# Function to evaluate model on test set
def evaluate_model(model, test_generator):
    # Evaluate on test set
    test_loss, test_acc = model.evaluate(test_generator)
    print(f"Test accuracy: {test_acc:.4f}")
    print(f"Test loss: {test_loss:.4f}")

    # Get predictions
    test_generator.reset()
    y_pred = model.predict(test_generator)
    y_pred_classes = np.argmax(y_pred, axis=1)

    # Get true labels
    y_true = test_generator.classes

    # Generate confusion matrix
    cm = confusion_matrix(y_true, y_pred_classes)

    # Plot confusion matrix
    plt.figure(figsize=(10, 8))
    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
                xticklabels=class_names,
                yticklabels=class_names)
    plt.xlabel('Predicted')
    plt.ylabel('True')
    plt.title('Confusion Matrix')
    plt.tight_layout()
    plt.savefig('confusion_matrix.png')
    plt.show()

    # Print classification report
    report = classification_report(y_true, y_pred_classes, target_names=class_names)
    print("Classification Report:")
    print(report)

    return test_acc, y_pred_classes, y_true

# Train the baseline model
def train_model(model, model_name):
    callbacks = setup_callbacks(model_name)

    history = model.fit(
        train_generator,
        steps_per_epoch=steps_per_epoch,
        epochs=50,  # Start with a high number, early stopping will prevent overfitting
        validation_data=validation_generator,
        validation_steps=validation_steps,
        callbacks=callbacks,
        verbose=1
    )

    # Plot learning curves
    plot_learning_curves(history, model_name)

    # Evaluate on test set
    test_acc, y_pred_classes, y_true = evaluate_model(model, test_generator)

    return history, test_acc, y_pred_classes, y_true

# Train the baseline model
baseline_history, baseline_test_acc, baseline_y_pred, baseline_y_true = train_model(baseline_model, 'baseline')
/usr/local/lib/python3.11/dist-packages/keras/src/trainers/data_adapters/py_dataset_adapter.py:121: UserWarning: Your `PyDataset` class should call `super().__init__(**kwargs)` in its constructor. `**kwargs` can include `workers`, `use_multiprocessing`, `max_queue_size`. Do not pass these arguments to `fit()`, as they will be ignored.
  self._warn_if_super_not_called()
Epoch 1/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 10s/step - accuracy: 0.1644 - loss: 2.1276
Epoch 1: val_accuracy improved from -inf to 0.41250, saving model to model_checkpoints/baseline_best.h5
/usr/local/lib/python3.11/dist-packages/keras/src/trainers/epoch_iterator.py:107: UserWarning: Your input ran out of data; interrupting training. Make sure that your dataset or generator can generate at least `steps_per_epoch * epochs` batches. You may need to use the `.repeat()` function when building your dataset.
  self._interrupted_warning()
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 298s 13s/step - accuracy: 0.1667 - loss: 2.1222 - val_accuracy: 0.4125 - val_loss: 1.5226
Epoch 2/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 10ms/step - accuracy: 0.4062 - loss: 1.4679
Epoch 2: val_accuracy improved from 0.41250 to 0.46875, saving model to model_checkpoints/baseline_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 448ms/step - accuracy: 0.4062 - loss: 1.4679 - val_accuracy: 0.4688 - val_loss: 1.3766
Epoch 3/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.4987 - loss: 1.2430
Epoch 3: val_accuracy improved from 0.46875 to 0.51250, saving model to model_checkpoints/baseline_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 52s 2s/step - accuracy: 0.5009 - loss: 1.2375 - val_accuracy: 0.5125 - val_loss: 1.1320
Epoch 4/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 10ms/step - accuracy: 0.3750 - loss: 1.2923
Epoch 4: val_accuracy improved from 0.51250 to 0.59375, saving model to model_checkpoints/baseline_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 10s 471ms/step - accuracy: 0.3750 - loss: 1.2923 - val_accuracy: 0.5938 - val_loss: 0.8631
Epoch 5/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.6580 - loss: 0.8595
Epoch 5: val_accuracy improved from 0.59375 to 0.81250, saving model to model_checkpoints/baseline_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 52s 2s/step - accuracy: 0.6604 - loss: 0.8554 - val_accuracy: 0.8125 - val_loss: 0.4519
Epoch 6/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.8750 - loss: 0.4260
Epoch 6: val_accuracy did not improve from 0.81250
22/22 ━━━━━━━━━━━━━━━━━━━━ 12s 564ms/step - accuracy: 0.8750 - loss: 0.4260 - val_accuracy: 0.7125 - val_loss: 0.7906
Epoch 7/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.8020 - loss: 0.4974
Epoch 7: val_accuracy improved from 0.81250 to 0.88750, saving model to model_checkpoints/baseline_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 50s 2s/step - accuracy: 0.8028 - loss: 0.4974 - val_accuracy: 0.8875 - val_loss: 0.5080
Epoch 8/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.9062 - loss: 0.4278
Epoch 8: val_accuracy did not improve from 0.88750
22/22 ━━━━━━━━━━━━━━━━━━━━ 12s 564ms/step - accuracy: 0.9062 - loss: 0.4278 - val_accuracy: 0.7250 - val_loss: 0.7963
Epoch 9/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.7814 - loss: 0.5327
Epoch 9: val_accuracy did not improve from 0.88750
22/22 ━━━━━━━━━━━━━━━━━━━━ 48s 2s/step - accuracy: 0.7817 - loss: 0.5313 - val_accuracy: 0.8375 - val_loss: 0.3678
Epoch 10/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.9688 - loss: 0.1343
Epoch 10: val_accuracy did not improve from 0.88750
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 434ms/step - accuracy: 0.9688 - loss: 0.1343 - val_accuracy: 0.8813 - val_loss: 0.3014
Epoch 11/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.8892 - loss: 0.2732
Epoch 11: val_accuracy did not improve from 0.88750
22/22 ━━━━━━━━━━━━━━━━━━━━ 48s 2s/step - accuracy: 0.8879 - loss: 0.2772 - val_accuracy: 0.7375 - val_loss: 0.6780
Epoch 12/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.7812 - loss: 0.6513
Epoch 12: val_accuracy did not improve from 0.88750
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 424ms/step - accuracy: 0.7812 - loss: 0.6513 - val_accuracy: 0.8188 - val_loss: 0.4738
Epoch 13/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.8844 - loss: 0.3126
Epoch 13: val_accuracy improved from 0.88750 to 0.90000, saving model to model_checkpoints/baseline_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 49s 2s/step - accuracy: 0.8847 - loss: 0.3129 - val_accuracy: 0.9000 - val_loss: 0.2686
Epoch 14/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.9688 - loss: 0.3303
Epoch 14: val_accuracy improved from 0.90000 to 0.91250, saving model to model_checkpoints/baseline_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 10s 467ms/step - accuracy: 0.9688 - loss: 0.3303 - val_accuracy: 0.9125 - val_loss: 0.2630
Epoch 15/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.9396 - loss: 0.1784
Epoch 15: val_accuracy improved from 0.91250 to 0.93125, saving model to model_checkpoints/baseline_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 53s 2s/step - accuracy: 0.9400 - loss: 0.1775 - val_accuracy: 0.9312 - val_loss: 0.1915
Epoch 16/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.9688 - loss: 0.1031
Epoch 16: val_accuracy did not improve from 0.93125
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 426ms/step - accuracy: 0.9688 - loss: 0.1031 - val_accuracy: 0.9000 - val_loss: 0.2578
Epoch 17/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.9339 - loss: 0.1657
Epoch 17: val_accuracy did not improve from 0.93125
22/22 ━━━━━━━━━━━━━━━━━━━━ 51s 2s/step - accuracy: 0.9325 - loss: 0.1699 - val_accuracy: 0.7312 - val_loss: 0.7976
Epoch 18/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.8750 - loss: 0.5317
Epoch 18: val_accuracy did not improve from 0.93125
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 433ms/step - accuracy: 0.8750 - loss: 0.5317 - val_accuracy: 0.8313 - val_loss: 0.5752
Epoch 19/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.8960 - loss: 0.3196
Epoch 19: val_accuracy did not improve from 0.93125
22/22 ━━━━━━━━━━━━━━━━━━━━ 48s 2s/step - accuracy: 0.8956 - loss: 0.3185 - val_accuracy: 0.9187 - val_loss: 0.2342
Epoch 20/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.9688 - loss: 0.0974
Epoch 20: val_accuracy did not improve from 0.93125
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 430ms/step - accuracy: 0.9688 - loss: 0.0974 - val_accuracy: 0.9125 - val_loss: 0.2786
Epoch 21/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.9678 - loss: 0.1150
Epoch 21: val_accuracy did not improve from 0.93125
22/22 ━━━━━━━━━━━━━━━━━━━━ 48s 2s/step - accuracy: 0.9680 - loss: 0.1142 - val_accuracy: 0.9000 - val_loss: 0.2522
Epoch 22/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.9688 - loss: 0.0638
Epoch 22: val_accuracy did not improve from 0.93125
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 432ms/step - accuracy: 0.9688 - loss: 0.0638 - val_accuracy: 0.8938 - val_loss: 0.2988
Epoch 23/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.9324 - loss: 0.1859
Epoch 23: val_accuracy did not improve from 0.93125
22/22 ━━━━━━━━━━━━━━━━━━━━ 48s 2s/step - accuracy: 0.9326 - loss: 0.1859 - val_accuracy: 0.9000 - val_loss: 0.2538
Epoch 24/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.9062 - loss: 0.1700
Epoch 24: val_accuracy did not improve from 0.93125
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 432ms/step - accuracy: 0.9062 - loss: 0.1700 - val_accuracy: 0.8500 - val_loss: 0.3775
Epoch 25/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.9452 - loss: 0.1555
Epoch 25: val_accuracy improved from 0.93125 to 0.96875, saving model to model_checkpoints/baseline_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 50s 2s/step - accuracy: 0.9462 - loss: 0.1532 - val_accuracy: 0.9688 - val_loss: 0.1087
Epoch 26/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.9688 - loss: 0.0371
Epoch 26: val_accuracy did not improve from 0.96875
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 439ms/step - accuracy: 0.9688 - loss: 0.0371 - val_accuracy: 0.9688 - val_loss: 0.1050
Epoch 27/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.9886 - loss: 0.0528
Epoch 27: val_accuracy improved from 0.96875 to 0.97500, saving model to model_checkpoints/baseline_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 52s 2s/step - accuracy: 0.9888 - loss: 0.0522 - val_accuracy: 0.9750 - val_loss: 0.0816
Epoch 28/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 1.0000 - loss: 0.0160
Epoch 28: val_accuracy did not improve from 0.97500
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 429ms/step - accuracy: 1.0000 - loss: 0.0160 - val_accuracy: 0.9438 - val_loss: 0.0804
Epoch 29/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.9892 - loss: 0.0259
Epoch 29: val_accuracy did not improve from 0.97500
22/22 ━━━━━━━━━━━━━━━━━━━━ 51s 2s/step - accuracy: 0.9895 - loss: 0.0256 - val_accuracy: 0.9625 - val_loss: 0.0927
Epoch 30/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 1.0000 - loss: 0.0056
Epoch 30: val_accuracy did not improve from 0.97500
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 439ms/step - accuracy: 1.0000 - loss: 0.0056 - val_accuracy: 0.9625 - val_loss: 0.1011
Epoch 31/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.9990 - loss: 0.0087
Epoch 31: val_accuracy did not improve from 0.97500
22/22 ━━━━━━━━━━━━━━━━━━━━ 49s 2s/step - accuracy: 0.9988 - loss: 0.0089 - val_accuracy: 0.9375 - val_loss: 0.1854
Epoch 32/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.9688 - loss: 0.0385
Epoch 32: val_accuracy improved from 0.97500 to 0.98750, saving model to model_checkpoints/baseline_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 11s 508ms/step - accuracy: 0.9688 - loss: 0.0385 - val_accuracy: 0.9875 - val_loss: 0.0889
Epoch 33/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.9926 - loss: 0.0203
Epoch 33: val_accuracy did not improve from 0.98750
22/22 ━━━━━━━━━━━━━━━━━━━━ 51s 2s/step - accuracy: 0.9919 - loss: 0.0219 - val_accuracy: 0.9438 - val_loss: 0.1398
Epoch 34/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.9688 - loss: 0.0347
Epoch 34: val_accuracy did not improve from 0.98750
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 432ms/step - accuracy: 0.9688 - loss: 0.0347 - val_accuracy: 0.9688 - val_loss: 0.1029
Epoch 35/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.9831 - loss: 0.0777
Epoch 35: val_accuracy did not improve from 0.98750
22/22 ━━━━━━━━━━━━━━━━━━━━ 48s 2s/step - accuracy: 0.9830 - loss: 0.0781 - val_accuracy: 0.9375 - val_loss: 0.2182
Epoch 36/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.9688 - loss: 0.1226
Epoch 36: val_accuracy did not improve from 0.98750
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 428ms/step - accuracy: 0.9688 - loss: 0.1226 - val_accuracy: 0.9375 - val_loss: 0.1802
Epoch 37/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.9772 - loss: 0.0778
Epoch 37: val_accuracy did not improve from 0.98750
22/22 ━━━━━━━━━━━━━━━━━━━━ 48s 2s/step - accuracy: 0.9773 - loss: 0.0776 - val_accuracy: 0.9625 - val_loss: 0.1035
Epoch 38/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 1.0000 - loss: 0.0165
Epoch 38: val_accuracy did not improve from 0.98750
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 427ms/step - accuracy: 1.0000 - loss: 0.0165 - val_accuracy: 0.9375 - val_loss: 0.1392
Epoch 39/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.9892 - loss: 0.0343
Epoch 39: val_accuracy did not improve from 0.98750
22/22 ━━━━━━━━━━━━━━━━━━━━ 48s 2s/step - accuracy: 0.9892 - loss: 0.0342 - val_accuracy: 0.9563 - val_loss: 0.1852
Epoch 40/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 1.0000 - loss: 0.0141
Epoch 40: val_accuracy did not improve from 0.98750
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 432ms/step - accuracy: 1.0000 - loss: 0.0141 - val_accuracy: 0.9625 - val_loss: 0.1956
Epoch 41/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.9989 - loss: 0.0136
Epoch 41: val_accuracy did not improve from 0.98750
22/22 ━━━━━━━━━━━━━━━━━━━━ 48s 2s/step - accuracy: 0.9989 - loss: 0.0134 - val_accuracy: 0.9563 - val_loss: 0.1448
Epoch 42/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 1.0000 - loss: 7.5742e-04
Epoch 42: val_accuracy did not improve from 0.98750
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 432ms/step - accuracy: 1.0000 - loss: 7.5742e-04 - val_accuracy: 0.9625 - val_loss: 0.1372
Epoch 42: early stopping
Restoring model weights from the end of the best epoch: 32.
No description has been provided for this image
6/6 ━━━━━━━━━━━━━━━━━━━━ 39s 7s/step - accuracy: 0.9872 - loss: 0.0500
Test accuracy: 0.9814
Test loss: 0.0688
6/6 ━━━━━━━━━━━━━━━━━━━━ 10s 1s/step
No description has been provided for this image
Classification Report:
              precision    recall  f1-score   support

       th_10       1.00      1.00      1.00        20
       th_11       1.00      1.00      1.00        20
       th_12       1.00      0.95      0.97        20
       th_13       1.00      1.00      1.00        20
       th_14       0.95      1.00      0.98        20
       th_15       1.00      1.00      1.00        22
       th_16       0.90      1.00      0.95        18
       th_17       1.00      0.90      0.95        21

    accuracy                           0.98       161
   macro avg       0.98      0.98      0.98       161
weighted avg       0.98      0.98      0.98       161

CNN Architecture Experiments¶

In [14]:
# Create different CNN architectures to experiment with
def create_model_v2(input_shape, num_classes):
    """
    Second version with more filters
    """
    model = keras.Sequential([
        # First convolutional block
        layers.Conv2D(64, (3, 3), activation='relu', padding='same', input_shape=input_shape),
        layers.MaxPooling2D((2, 2)),

        # Second convolutional block
        layers.Conv2D(128, (3, 3), activation='relu', padding='same'),
        layers.MaxPooling2D((2, 2)),

        # Third convolutional block
        layers.Conv2D(256, (3, 3), activation='relu', padding='same'),
        layers.MaxPooling2D((2, 2)),

        # Fourth convolutional block
        layers.Conv2D(512, (3, 3), activation='relu', padding='same'),
        layers.MaxPooling2D((2, 2)),

        # Flatten and dense layers
        layers.Flatten(),
        layers.Dense(1024, activation='relu'),
        layers.Dense(num_classes, activation='softmax')
    ])

    model.compile(
        optimizer='adam',
        loss='categorical_crossentropy',
        metrics=['accuracy']
    )

    return model

def create_model_v3(input_shape, num_classes):
    """
    Third version with deeper architecture (more layers)
    """
    model = keras.Sequential([
        # First convolutional block
        layers.Conv2D(32, (3, 3), activation='relu', padding='same', input_shape=input_shape),
        layers.Conv2D(32, (3, 3), activation='relu', padding='same'),
        layers.MaxPooling2D((2, 2)),

        # Second convolutional block
        layers.Conv2D(64, (3, 3), activation='relu', padding='same'),
        layers.Conv2D(64, (3, 3), activation='relu', padding='same'),
        layers.MaxPooling2D((2, 2)),

        # Third convolutional block
        layers.Conv2D(128, (3, 3), activation='relu', padding='same'),
        layers.Conv2D(128, (3, 3), activation='relu', padding='same'),
        layers.MaxPooling2D((2, 2)),

        # Fourth convolutional block
        layers.Conv2D(256, (3, 3), activation='relu', padding='same'),
        layers.Conv2D(256, (3, 3), activation='relu', padding='same'),
        layers.MaxPooling2D((2, 2)),

        # Flatten and dense layers
        layers.Flatten(),
        layers.Dense(512, activation='relu'),
        layers.Dense(num_classes, activation='softmax')
    ])

    model.compile(
        optimizer='adam',
        loss='categorical_crossentropy',
        metrics=['accuracy']
    )

    return model

def create_model_v4(input_shape, num_classes):
    """
    Fourth version with different filter sizes
    """
    model = keras.Sequential([
        # First convolutional block
        layers.Conv2D(32, (5, 5), activation='relu', padding='same', input_shape=input_shape),
        layers.MaxPooling2D((2, 2)),

        # Second convolutional block
        layers.Conv2D(64, (5, 5), activation='relu', padding='same'),
        layers.MaxPooling2D((2, 2)),

        # Third convolutional block
        layers.Conv2D(128, (3, 3), activation='relu', padding='same'),
        layers.MaxPooling2D((2, 2)),

        # Fourth convolutional block
        layers.Conv2D(256, (3, 3), activation='relu', padding='same'),
        layers.MaxPooling2D((2, 2)),

        # Flatten and dense layers
        layers.Flatten(),
        layers.Dense(512, activation='relu'),
        layers.Dense(num_classes, activation='softmax')
    ])

    model.compile(
        optimizer='adam',
        loss='categorical_crossentropy',
        metrics=['accuracy']
    )

    return model

# Dictionary to store results
model_results = {
    'baseline': {
        'val_accuracy': None,
        'test_accuracy': baseline_test_acc,
        'description': 'Standard CNN with 4 conv blocks, filter sizes: 32-64-128-256'
    }
}

# Train and evaluate additional models
models_to_try = [
    ('model_v2', create_model_v2, 'More filters: 64-128-256-512, dense: 1024'),
    ('model_v3', create_model_v3, 'Deeper architecture with double conv layers per block'),
    ('model_v4', create_model_v4, 'Different filter sizes: 5x5 in early layers, 3x3 in later layers')
]

# Function to train all model variants
def train_all_model_variants():
    for model_name, model_func, description in models_to_try:
        print(f"\n{'='*50}")
        print(f"Training {model_name}: {description}")
        print(f"{'='*50}")

        # Create the model
        model = model_func(input_shape, num_classes)
        model.summary()

        # Train and evaluate
        history, test_acc, _, _ = train_model(model, model_name)

        # Store results
        val_acc = max(history.history['val_accuracy'])
        model_results[model_name] = {
            'val_accuracy': val_acc,
            'test_accuracy': test_acc,
            'description': description
        }

    # Print comparison table
    print("\n\nModel Comparison:")
    print(f"{'Model':<12} {'Val Acc':<10} {'Test Acc':<10} {'Description'}")
    print(f"{'-'*80}")

    for model_name, results in model_results.items():
        val_acc = results['val_accuracy']
        test_acc = results['test_accuracy']
        desc = results['description']

        val_acc_str = f"{val_acc:.4f}" if val_acc is not None else "N/A"
        test_acc_str = f"{test_acc:.4f}" if test_acc is not None else "N/A"

        print(f"{model_name:<12} {val_acc_str:<10} {test_acc_str:<10} {desc}")

    # Find best model
    best_model = max(model_results.items(), key=lambda x: x[1]['test_accuracy'])
    print(f"\nBest model: {best_model[0]} with test accuracy: {best_model[1]['test_accuracy']:.4f}")

    return best_model[0]

# Run the model training experiment - just try model_v3 (deeper architecture)
print("\n{'='*50}")
print(f"Training model_v3: Deeper architecture with double conv layers per block")
print(f"{'='*50}")

# Create the model
model_v3 = create_model_v3(input_shape, num_classes)
model_v3.summary()

# Train and evaluate
history_v3, test_acc_v3, _, _ = train_model(model_v3, 'model_v3')

# Store results
val_acc_v3 = max(history_v3.history['val_accuracy'])
model_results['model_v3'] = {
    'val_accuracy': val_acc_v3,
    'test_accuracy': test_acc_v3,
    'description': 'Deeper architecture with double conv layers per block'
}

# Print comparison table
print("\n\nModel Comparison:")
print(f"{'Model':<12} {'Val Acc':<10} {'Test Acc':<10} {'Description'}")
print(f"{'-'*80}")

for model_name, results in model_results.items():
    val_acc = results['val_accuracy']
    test_acc = results['test_accuracy']
    desc = results['description']

    val_acc_str = f"{val_acc:.4f}" if val_acc is not None else "N/A"
    test_acc_str = f"{test_acc:.4f}" if test_acc is not None else "N/A"

    print(f"{model_name:<12} {val_acc_str:<10} {test_acc_str:<10} {desc}")

# Determine if model_v3 improves over baseline
if test_acc_v3 > baseline_test_acc:
    print(f"\nModel v3 improves over baseline by {(test_acc_v3 - baseline_test_acc)*100:.2f}%")
    best_model_name = 'model_v3'
else:
    print(f"\nModel v3 does not improve over baseline. Staying with baseline model.")
    best_model_name = 'baseline'
{'='*50}
Training model_v3: Deeper architecture with double conv layers per block
==================================================
/usr/local/lib/python3.11/dist-packages/keras/src/layers/convolutional/base_conv.py:107: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(activity_regularizer=activity_regularizer, **kwargs)
Model: "sequential_1"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ conv2d_4 (Conv2D)               │ (None, 103, 224, 32)   │           896 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_5 (Conv2D)               │ (None, 103, 224, 32)   │         9,248 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_4 (MaxPooling2D)  │ (None, 51, 112, 32)    │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_6 (Conv2D)               │ (None, 51, 112, 64)    │        18,496 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_7 (Conv2D)               │ (None, 51, 112, 64)    │        36,928 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_5 (MaxPooling2D)  │ (None, 25, 56, 64)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_8 (Conv2D)               │ (None, 25, 56, 128)    │        73,856 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_9 (Conv2D)               │ (None, 25, 56, 128)    │       147,584 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_6 (MaxPooling2D)  │ (None, 12, 28, 128)    │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_10 (Conv2D)              │ (None, 12, 28, 256)    │       295,168 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_11 (Conv2D)              │ (None, 12, 28, 256)    │       590,080 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_7 (MaxPooling2D)  │ (None, 6, 14, 256)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ flatten_1 (Flatten)             │ (None, 21504)          │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_2 (Dense)                 │ (None, 512)            │    11,010,560 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_3 (Dense)                 │ (None, 8)              │         4,104 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
 Total params: 12,186,920 (46.49 MB)
 Trainable params: 12,186,920 (46.49 MB)
 Non-trainable params: 0 (0.00 B)
Epoch 1/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.1235 - loss: 2.1261
Epoch 1: val_accuracy improved from -inf to 0.12500, saving model to model_checkpoints/model_v3_best.h5
/usr/local/lib/python3.11/dist-packages/keras/src/trainers/epoch_iterator.py:107: UserWarning: Your input ran out of data; interrupting training. Make sure that your dataset or generator can generate at least `steps_per_epoch * epochs` batches. You may need to use the `.repeat()` function when building your dataset.
  self._interrupted_warning()
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 57s 2s/step - accuracy: 0.1234 - loss: 2.1249 - val_accuracy: 0.1250 - val_loss: 2.0792
Epoch 2/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.0938 - loss: 2.0802
Epoch 2: val_accuracy did not improve from 0.12500
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 428ms/step - accuracy: 0.0938 - loss: 2.0802 - val_accuracy: 0.1250 - val_loss: 2.0792
Epoch 3/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.1150 - loss: 2.0801
Epoch 3: val_accuracy did not improve from 0.12500
22/22 ━━━━━━━━━━━━━━━━━━━━ 48s 2s/step - accuracy: 0.1148 - loss: 2.0801 - val_accuracy: 0.1250 - val_loss: 2.0786
Epoch 4/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.0312 - loss: 2.0800
Epoch 4: val_accuracy did not improve from 0.12500
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 442ms/step - accuracy: 0.0312 - loss: 2.0800 - val_accuracy: 0.1250 - val_loss: 2.0791
Epoch 5/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.1236 - loss: 2.0786
Epoch 5: val_accuracy improved from 0.12500 to 0.13750, saving model to model_checkpoints/model_v3_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 50s 2s/step - accuracy: 0.1237 - loss: 2.0786 - val_accuracy: 0.1375 - val_loss: 2.0789
Epoch 6/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 10ms/step - accuracy: 0.0588 - loss: 2.0806
Epoch 6: val_accuracy did not improve from 0.13750
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 444ms/step - accuracy: 0.0588 - loss: 2.0806 - val_accuracy: 0.1375 - val_loss: 2.0789
Epoch 7/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.1272 - loss: 2.0802
Epoch 7: val_accuracy did not improve from 0.13750
22/22 ━━━━━━━━━━━━━━━━━━━━ 51s 2s/step - accuracy: 0.1277 - loss: 2.0801 - val_accuracy: 0.1375 - val_loss: 2.0788
Epoch 8/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.0938 - loss: 2.0787
Epoch 8: val_accuracy did not improve from 0.13750
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 432ms/step - accuracy: 0.0938 - loss: 2.0787 - val_accuracy: 0.1375 - val_loss: 2.0788
Epoch 9/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.1512 - loss: 2.0791
Epoch 9: val_accuracy did not improve from 0.13750
22/22 ━━━━━━━━━━━━━━━━━━━━ 48s 2s/step - accuracy: 0.1505 - loss: 2.0791 - val_accuracy: 0.1375 - val_loss: 2.0787
Epoch 10/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.1562 - loss: 2.0740
Epoch 10: val_accuracy did not improve from 0.13750
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 427ms/step - accuracy: 0.1562 - loss: 2.0740 - val_accuracy: 0.1375 - val_loss: 2.0787
Epoch 11/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.1614 - loss: 2.0774
Epoch 11: val_accuracy did not improve from 0.13750
22/22 ━━━━━━━━━━━━━━━━━━━━ 47s 2s/step - accuracy: 0.1603 - loss: 2.0775 - val_accuracy: 0.1375 - val_loss: 2.0785
Epoch 12/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.1250 - loss: 2.0809
Epoch 12: val_accuracy did not improve from 0.13750
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 425ms/step - accuracy: 0.1250 - loss: 2.0809 - val_accuracy: 0.1375 - val_loss: 2.0785
Epoch 13/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.1229 - loss: 2.0800
Epoch 13: val_accuracy did not improve from 0.13750
22/22 ━━━━━━━━━━━━━━━━━━━━ 49s 2s/step - accuracy: 0.1236 - loss: 2.0799 - val_accuracy: 0.1375 - val_loss: 2.0785
Epoch 14/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.0938 - loss: 2.0734
Epoch 14: val_accuracy did not improve from 0.13750
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 436ms/step - accuracy: 0.0938 - loss: 2.0734 - val_accuracy: 0.1375 - val_loss: 2.0785
Epoch 15/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.1258 - loss: 2.0806
Epoch 15: val_accuracy did not improve from 0.13750
22/22 ━━━━━━━━━━━━━━━━━━━━ 48s 2s/step - accuracy: 0.1263 - loss: 2.0805 - val_accuracy: 0.1375 - val_loss: 2.0785
Epoch 15: early stopping
Restoring model weights from the end of the best epoch: 5.
No description has been provided for this image
6/6 ━━━━━━━━━━━━━━━━━━━━ 10s 2s/step - accuracy: 0.0832 - loss: 2.0788
Test accuracy: 0.1366
Test loss: 2.0788
6/6 ━━━━━━━━━━━━━━━━━━━━ 10s 1s/step
No description has been provided for this image
Classification Report:
              precision    recall  f1-score   support

       th_10       0.00      0.00      0.00        20
       th_11       0.00      0.00      0.00        20
       th_12       0.00      0.00      0.00        20
       th_13       0.00      0.00      0.00        20
       th_14       0.00      0.00      0.00        20
       th_15       0.14      1.00      0.24        22
       th_16       0.00      0.00      0.00        18
       th_17       0.00      0.00      0.00        21

    accuracy                           0.14       161
   macro avg       0.02      0.12      0.03       161
weighted avg       0.02      0.14      0.03       161



Model Comparison:
Model        Val Acc    Test Acc   Description
--------------------------------------------------------------------------------
baseline     N/A        0.9814     Standard CNN with 4 conv blocks, filter sizes: 32-64-128-256
model_v3     0.1375     0.1366     Deeper architecture with double conv layers per block

Model v3 does not improve over baseline. Staying with baseline model.
/usr/local/lib/python3.11/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))
/usr/local/lib/python3.11/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))
/usr/local/lib/python3.11/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))

Data Augmentation Implementation¶

In [15]:
# Define various data augmentation configurations to experiment with
def create_augmented_generator(base_dir, aug_config, name):
    """
    Create a data generator with specific augmentation settings
    """
    # Create training data generator with augmentations
    train_datagen = ImageDataGenerator(
        rescale=1./255,
        **aug_config
    )

    # Validation and test generators remain the same (no augmentation)
    validation_datagen = ImageDataGenerator(rescale=1./255)
    test_datagen = ImageDataGenerator(rescale=1./255)

    # Create generators
    train_generator = train_datagen.flow_from_directory(
        os.path.join(base_dir, 'train'),
        target_size=(IMG_HEIGHT, IMG_WIDTH),
        batch_size=BATCH_SIZE,
        class_mode='categorical',
        shuffle=True
    )

    validation_generator = validation_datagen.flow_from_directory(
        os.path.join(base_dir, 'validation'),
        target_size=(IMG_HEIGHT, IMG_WIDTH),
        batch_size=BATCH_SIZE,
        class_mode='categorical',
        shuffle=False
    )

    test_generator = test_datagen.flow_from_directory(
        os.path.join(base_dir, 'test'),
        target_size=(IMG_HEIGHT, IMG_WIDTH),
        batch_size=BATCH_SIZE,
        class_mode='categorical',
        shuffle=False
    )

    print(f"Created augmented generator: {name}")
    return train_generator, validation_generator, test_generator

# Define augmentation configurations to test
augmentation_configs = {
    'basic': {
        'rotation_range': 10,
        'width_shift_range': 0.1,
        'height_shift_range': 0.1,
        'zoom_range': 0.1,
        'horizontal_flip': True
    },
    'moderate': {
        'rotation_range': 20,
        'width_shift_range': 0.2,
        'height_shift_range': 0.2,
        'zoom_range': 0.2,
        'horizontal_flip': True,
        'vertical_flip': False,
        'brightness_range': [0.8, 1.2]
    },
    'aggressive': {
        'rotation_range': 30,
        'width_shift_range': 0.3,
        'height_shift_range': 0.3,
        'zoom_range': 0.3,
        'horizontal_flip': True,
        'vertical_flip': True,
        'brightness_range': [0.7, 1.3],
        'shear_range': 0.2,
        'fill_mode': 'nearest'
    }
}

# Function to train with different augmentation configurations
def train_with_augmentations(model_func, model_name, base_model_results):
    """
    Train the model with different augmentation configurations
    """
    augmentation_results = {
        'no_aug': base_model_results  # Include baseline without augmentation for comparison
    }

    for aug_name, aug_config in augmentation_configs.items():
        print(f"\n{'='*50}")
        print(f"Training with {aug_name} augmentation:")
        print(f"Config: {aug_config}")
        print(f"{'='*50}")

        # Create data generators with this augmentation
        aug_train_gen, aug_val_gen, aug_test_gen = create_augmented_generator(
            base_dir, aug_config, aug_name
        )

        # Create a fresh model
        model = model_func(input_shape, num_classes)

        # Setup callbacks
        callbacks = setup_callbacks(f"{model_name}_{aug_name}")

        # Train model
        history = model.fit(
            aug_train_gen,
            steps_per_epoch=steps_per_epoch,
            epochs=50,  # Early stopping will prevent overfitting
            validation_data=aug_val_gen,
            validation_steps=validation_steps,
            callbacks=callbacks,
            verbose=1
        )

        # Plot learning curves
        plot_learning_curves(history, f"{model_name}_{aug_name}")

        # Evaluate on test set
        test_loss, test_acc = model.evaluate(aug_test_gen)
        print(f"Test accuracy with {aug_name} augmentation: {test_acc:.4f}")

        # Store results
        val_acc = max(history.history['val_accuracy'])
        augmentation_results[aug_name] = {
            'val_accuracy': val_acc,
            'test_accuracy': test_acc,
            'config': aug_config
        }

    # Create a comparison table
    print("\n\nAugmentation Comparison for", model_name)
    print(f"{'Config':<12} {'Val Acc':<10} {'Test Acc':<10}")
    print(f"{'-'*40}")

    for aug_name, results in augmentation_results.items():
        val_acc = results['val_accuracy']
        test_acc = results['test_accuracy']
        print(f"{aug_name:<12} {val_acc:.4f} {test_acc:.4f}")

    # Find best augmentation
    best_aug = max(augmentation_results.items(), key=lambda x: x[1]['test_accuracy'])
    print(f"\nBest augmentation: {best_aug[0]} with test accuracy: {best_aug[1]['test_accuracy']:.4f}")

    # Compare with base model
    base_acc = base_model_results['test_accuracy']
    improvement = (best_aug[1]['test_accuracy'] - base_acc) * 100
    print(f"Improvement over base model: {improvement:.2f}%")

    return best_aug[0], best_aug[1]['test_accuracy'], augmentation_results

# Function to visualize augmented samples
def visualize_augmentations(aug_config):
    # Set up a figure to display original and augmented images
    plt.figure(figsize=(12, 6))

    # Create an augmentation generator for a single image
    datagen = ImageDataGenerator(**aug_config)

    # Get a sample image from the training set
    train_dir = os.path.join(base_dir, 'train')
    classes = os.listdir(train_dir)
    sample_class = classes[0]  # Use first class
    class_dir = os.path.join(train_dir, sample_class)
    sample_img_name = os.listdir(class_dir)[0]  # Use first image
    sample_img_path = os.path.join(class_dir, sample_img_name)

    # Load the image
    img = load_img(sample_img_path, target_size=(IMG_HEIGHT, IMG_WIDTH))
    x = img_to_array(img)
    x = x.reshape((1,) + x.shape)

    # Display original image
    plt.subplot(1, 5, 1)
    plt.imshow(img)
    plt.title('Original')
    plt.axis('off')

    # Generate and display augmented images
    i = 0
    for batch in datagen.flow(x, batch_size=1):
        plt.subplot(1, 5, i+2)
        plt.imshow(batch[0].astype('uint8'))
        plt.title(f'Augmented {i+1}')
        plt.axis('off')
        i += 1
        if i >= 4:  # Show 4 augmented examples
            break

    plt.tight_layout()
    plt.suptitle(f"Augmentation Examples")
    plt.show()

# This function will be called after we determine the best architecture
def run_data_augmentation(best_model_func, best_model_name, base_model_results):
    """
    Run data augmentation experiments with the best model architecture
    """
    # First, visualize the different augmentation strategies
    for aug_name, aug_config in augmentation_configs.items():
        print(f"\nVisualizing {aug_name} augmentation:")
        visualize_augmentations(aug_config)

    # Then train with each augmentation strategy
    best_aug, best_aug_acc, aug_results = train_with_augmentations(
        best_model_func, best_model_name, base_model_results
    )

    return best_aug, best_aug_acc, aug_results

# This will be used after determining the best model architecture
run_data_augmentation(create_baseline_model, 'baseline', model_results['baseline'])
Visualizing basic augmentation:
No description has been provided for this image
Visualizing moderate augmentation:
No description has been provided for this image
Visualizing aggressive augmentation:
No description has been provided for this image
==================================================
Training with basic augmentation:
Config: {'rotation_range': 10, 'width_shift_range': 0.1, 'height_shift_range': 0.1, 'zoom_range': 0.1, 'horizontal_flip': True}
==================================================
Found 721 images belonging to 8 classes.
Found 160 images belonging to 8 classes.
Found 161 images belonging to 8 classes.
Created augmented generator: basic
/usr/local/lib/python3.11/dist-packages/keras/src/layers/convolutional/base_conv.py:107: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(activity_regularizer=activity_regularizer, **kwargs)
/usr/local/lib/python3.11/dist-packages/keras/src/trainers/data_adapters/py_dataset_adapter.py:121: UserWarning: Your `PyDataset` class should call `super().__init__(**kwargs)` in its constructor. `**kwargs` can include `workers`, `use_multiprocessing`, `max_queue_size`. Do not pass these arguments to `fit()`, as they will be ignored.
  self._warn_if_super_not_called()
Epoch 1/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.1323 - loss: 2.2138
Epoch 1: val_accuracy improved from -inf to 0.16875, saving model to model_checkpoints/baseline_basic_best.h5
/usr/local/lib/python3.11/dist-packages/keras/src/trainers/epoch_iterator.py:107: UserWarning: Your input ran out of data; interrupting training. Make sure that your dataset or generator can generate at least `steps_per_epoch * epochs` batches. You may need to use the `.repeat()` function when building your dataset.
  self._interrupted_warning()
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 57s 2s/step - accuracy: 0.1324 - loss: 2.2091 - val_accuracy: 0.1688 - val_loss: 1.8824
Epoch 2/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 10ms/step - accuracy: 0.1250 - loss: 2.0036
Epoch 2: val_accuracy improved from 0.16875 to 0.23125, saving model to model_checkpoints/baseline_basic_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 449ms/step - accuracy: 0.1250 - loss: 2.0036 - val_accuracy: 0.2313 - val_loss: 1.8199
Epoch 3/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.2313 - loss: 1.7520
Epoch 3: val_accuracy improved from 0.23125 to 0.25000, saving model to model_checkpoints/baseline_basic_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 52s 2s/step - accuracy: 0.2330 - loss: 1.7485 - val_accuracy: 0.2500 - val_loss: 1.6179
Epoch 4/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.1875 - loss: 1.8481
Epoch 4: val_accuracy improved from 0.25000 to 0.33125, saving model to model_checkpoints/baseline_basic_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 13s 595ms/step - accuracy: 0.1875 - loss: 1.8481 - val_accuracy: 0.3313 - val_loss: 1.5744
Epoch 5/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.3502 - loss: 1.6603
Epoch 5: val_accuracy improved from 0.33125 to 0.38125, saving model to model_checkpoints/baseline_basic_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 57s 3s/step - accuracy: 0.3533 - loss: 1.6531 - val_accuracy: 0.3812 - val_loss: 1.2693
Epoch 6/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.6562 - loss: 0.8936
Epoch 6: val_accuracy improved from 0.38125 to 0.43125, saving model to model_checkpoints/baseline_basic_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 10s 460ms/step - accuracy: 0.6562 - loss: 0.8936 - val_accuracy: 0.4313 - val_loss: 1.5594
Epoch 7/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.5434 - loss: 1.1540
Epoch 7: val_accuracy did not improve from 0.43125
22/22 ━━━━━━━━━━━━━━━━━━━━ 55s 3s/step - accuracy: 0.5433 - loss: 1.1539 - val_accuracy: 0.2125 - val_loss: 2.2650
Epoch 8/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.6875 - loss: 1.0881
Epoch 8: val_accuracy did not improve from 0.43125
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 447ms/step - accuracy: 0.6875 - loss: 1.0881 - val_accuracy: 0.2562 - val_loss: 2.1367
Epoch 9/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.5674 - loss: 0.9820
Epoch 9: val_accuracy improved from 0.43125 to 0.69375, saving model to model_checkpoints/baseline_basic_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 54s 2s/step - accuracy: 0.5694 - loss: 0.9777 - val_accuracy: 0.6938 - val_loss: 0.7256
Epoch 10/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 10ms/step - accuracy: 0.6875 - loss: 0.8321
Epoch 10: val_accuracy improved from 0.69375 to 0.70625, saving model to model_checkpoints/baseline_basic_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 10s 483ms/step - accuracy: 0.6875 - loss: 0.8321 - val_accuracy: 0.7063 - val_loss: 0.7432
Epoch 11/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.6317 - loss: 0.8174
Epoch 11: val_accuracy did not improve from 0.70625
22/22 ━━━━━━━━━━━━━━━━━━━━ 52s 2s/step - accuracy: 0.6329 - loss: 0.8156 - val_accuracy: 0.7000 - val_loss: 0.7911
Epoch 12/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.6875 - loss: 0.5780
Epoch 12: val_accuracy did not improve from 0.70625
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 425ms/step - accuracy: 0.6875 - loss: 0.5780 - val_accuracy: 0.6625 - val_loss: 0.7025
Epoch 13/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.7248 - loss: 0.6598
Epoch 13: val_accuracy did not improve from 0.70625
22/22 ━━━━━━━━━━━━━━━━━━━━ 50s 2s/step - accuracy: 0.7250 - loss: 0.6584 - val_accuracy: 0.5938 - val_loss: 1.5801
Epoch 14/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 10ms/step - accuracy: 0.8125 - loss: 0.4913
Epoch 14: val_accuracy did not improve from 0.70625
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 421ms/step - accuracy: 0.8125 - loss: 0.4913 - val_accuracy: 0.5813 - val_loss: 1.6777
Epoch 15/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.7252 - loss: 0.6649
Epoch 15: val_accuracy did not improve from 0.70625
22/22 ━━━━━━━━━━━━━━━━━━━━ 51s 2s/step - accuracy: 0.7261 - loss: 0.6622 - val_accuracy: 0.6750 - val_loss: 0.7675
Epoch 16/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.6875 - loss: 0.7224
Epoch 16: val_accuracy did not improve from 0.70625
22/22 ━━━━━━━━━━━━━━━━━━━━ 10s 453ms/step - accuracy: 0.6875 - loss: 0.7224 - val_accuracy: 0.6625 - val_loss: 0.7555
Epoch 17/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.7769 - loss: 0.5533
Epoch 17: val_accuracy did not improve from 0.70625
22/22 ━━━━━━━━━━━━━━━━━━━━ 52s 2s/step - accuracy: 0.7782 - loss: 0.5512 - val_accuracy: 0.6000 - val_loss: 1.3300
Epoch 18/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.7812 - loss: 0.5307
Epoch 18: val_accuracy did not improve from 0.70625
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 441ms/step - accuracy: 0.7812 - loss: 0.5307 - val_accuracy: 0.5250 - val_loss: 1.9388
Epoch 19/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.8548 - loss: 0.3812
Epoch 19: val_accuracy did not improve from 0.70625
22/22 ━━━━━━━━━━━━━━━━━━━━ 51s 2s/step - accuracy: 0.8548 - loss: 0.3814 - val_accuracy: 0.6125 - val_loss: 2.3040
Epoch 20/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.8438 - loss: 0.3307
Epoch 20: val_accuracy did not improve from 0.70625
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 434ms/step - accuracy: 0.8438 - loss: 0.3307 - val_accuracy: 0.5063 - val_loss: 3.0129
Epoch 20: early stopping
Restoring model weights from the end of the best epoch: 10.
No description has been provided for this image
6/6 ━━━━━━━━━━━━━━━━━━━━ 9s 2s/step - accuracy: 0.7197 - loss: 0.6465
Test accuracy with basic augmentation: 0.7329

==================================================
Training with moderate augmentation:
Config: {'rotation_range': 20, 'width_shift_range': 0.2, 'height_shift_range': 0.2, 'zoom_range': 0.2, 'horizontal_flip': True, 'vertical_flip': False, 'brightness_range': [0.8, 1.2]}
==================================================
Found 721 images belonging to 8 classes.
Found 160 images belonging to 8 classes.
Found 161 images belonging to 8 classes.
Created augmented generator: moderate
Epoch 1/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.1134 - loss: 2.1127
Epoch 1: val_accuracy improved from -inf to 0.22500, saving model to model_checkpoints/baseline_moderate_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 59s 3s/step - accuracy: 0.1136 - loss: 2.1119 - val_accuracy: 0.2250 - val_loss: 2.0370
Epoch 2/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.0625 - loss: 2.0654
Epoch 2: val_accuracy did not improve from 0.22500
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 437ms/step - accuracy: 0.0625 - loss: 2.0654 - val_accuracy: 0.2250 - val_loss: 2.0165
Epoch 3/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.1649 - loss: 2.0046
Epoch 3: val_accuracy did not improve from 0.22500
22/22 ━━━━━━━━━━━━━━━━━━━━ 53s 2s/step - accuracy: 0.1657 - loss: 2.0023 - val_accuracy: 0.1375 - val_loss: 2.0123
Epoch 4/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.1562 - loss: 1.7703
Epoch 4: val_accuracy improved from 0.22500 to 0.23750, saving model to model_checkpoints/baseline_moderate_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 11s 524ms/step - accuracy: 0.1562 - loss: 1.7703 - val_accuracy: 0.2375 - val_loss: 1.6578
Epoch 5/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.2549 - loss: 1.6866
Epoch 5: val_accuracy improved from 0.23750 to 0.30625, saving model to model_checkpoints/baseline_moderate_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 56s 3s/step - accuracy: 0.2545 - loss: 1.6881 - val_accuracy: 0.3063 - val_loss: 1.5660
Epoch 6/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.0938 - loss: 1.6941
Epoch 6: val_accuracy did not improve from 0.30625
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 422ms/step - accuracy: 0.0938 - loss: 1.6941 - val_accuracy: 0.2188 - val_loss: 1.5761
Epoch 7/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.2812 - loss: 1.5599
Epoch 7: val_accuracy did not improve from 0.30625
22/22 ━━━━━━━━━━━━━━━━━━━━ 54s 2s/step - accuracy: 0.2822 - loss: 1.5594 - val_accuracy: 0.2062 - val_loss: 2.5094
Epoch 8/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.4375 - loss: 1.3699
Epoch 8: val_accuracy improved from 0.30625 to 0.36250, saving model to model_checkpoints/baseline_moderate_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 10s 490ms/step - accuracy: 0.4375 - loss: 1.3699 - val_accuracy: 0.3625 - val_loss: 2.0725
Epoch 9/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.4571 - loss: 1.2931
Epoch 9: val_accuracy did not improve from 0.36250
22/22 ━━━━━━━━━━━━━━━━━━━━ 54s 2s/step - accuracy: 0.4558 - loss: 1.2941 - val_accuracy: 0.2500 - val_loss: 2.9051
Epoch 10/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.4062 - loss: 1.1454
Epoch 10: val_accuracy did not improve from 0.36250
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 425ms/step - accuracy: 0.4062 - loss: 1.1454 - val_accuracy: 0.1750 - val_loss: 4.7767
Epoch 11/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.4321 - loss: 1.3103
Epoch 11: val_accuracy did not improve from 0.36250
22/22 ━━━━━━━━━━━━━━━━━━━━ 52s 2s/step - accuracy: 0.4321 - loss: 1.3103 - val_accuracy: 0.3562 - val_loss: 1.6350
Epoch 12/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 10ms/step - accuracy: 0.3125 - loss: 1.2344
Epoch 12: val_accuracy did not improve from 0.36250
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 426ms/step - accuracy: 0.3125 - loss: 1.2344 - val_accuracy: 0.2688 - val_loss: 1.8917
Epoch 13/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.5084 - loss: 1.0967
Epoch 13: val_accuracy did not improve from 0.36250
22/22 ━━━━━━━━━━━━━━━━━━━━ 51s 2s/step - accuracy: 0.5076 - loss: 1.0988 - val_accuracy: 0.3313 - val_loss: 2.9077
Epoch 14/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.6250 - loss: 0.9274
Epoch 14: val_accuracy did not improve from 0.36250
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 423ms/step - accuracy: 0.6250 - loss: 0.9274 - val_accuracy: 0.1562 - val_loss: 4.5437
Epoch 15/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.4434 - loss: 1.2203
Epoch 15: val_accuracy did not improve from 0.36250
22/22 ━━━━━━━━━━━━━━━━━━━━ 51s 2s/step - accuracy: 0.4451 - loss: 1.2170 - val_accuracy: 0.3562 - val_loss: 2.4511
Epoch 16/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.4688 - loss: 1.0483
Epoch 16: val_accuracy did not improve from 0.36250
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 424ms/step - accuracy: 0.4688 - loss: 1.0483 - val_accuracy: 0.3125 - val_loss: 2.8728
Epoch 17/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.5219 - loss: 1.0279
Epoch 17: val_accuracy improved from 0.36250 to 0.53125, saving model to model_checkpoints/baseline_moderate_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 53s 2s/step - accuracy: 0.5227 - loss: 1.0275 - val_accuracy: 0.5312 - val_loss: 1.2207
Epoch 18/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.5000 - loss: 1.0687
Epoch 18: val_accuracy did not improve from 0.53125
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 446ms/step - accuracy: 0.5000 - loss: 1.0687 - val_accuracy: 0.5125 - val_loss: 1.1153
Epoch 19/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.5648 - loss: 1.0294
Epoch 19: val_accuracy did not improve from 0.53125
22/22 ━━━━━━━━━━━━━━━━━━━━ 54s 2s/step - accuracy: 0.5648 - loss: 1.0287 - val_accuracy: 0.4187 - val_loss: 1.2529
Epoch 20/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.5000 - loss: 0.9544
Epoch 20: val_accuracy did not improve from 0.53125
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 423ms/step - accuracy: 0.5000 - loss: 0.9544 - val_accuracy: 0.3438 - val_loss: 1.8937
Epoch 21/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.5912 - loss: 0.9510
Epoch 21: val_accuracy did not improve from 0.53125
22/22 ━━━━━━━━━━━━━━━━━━━━ 51s 2s/step - accuracy: 0.5908 - loss: 0.9521 - val_accuracy: 0.3125 - val_loss: 2.0926
Epoch 22/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.5938 - loss: 0.9186
Epoch 22: val_accuracy did not improve from 0.53125
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 424ms/step - accuracy: 0.5938 - loss: 0.9186 - val_accuracy: 0.5125 - val_loss: 1.0189
Epoch 23/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.6159 - loss: 0.8886
Epoch 23: val_accuracy improved from 0.53125 to 0.65625, saving model to model_checkpoints/baseline_moderate_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 53s 2s/step - accuracy: 0.6160 - loss: 0.8889 - val_accuracy: 0.6562 - val_loss: 0.7887
Epoch 24/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.7188 - loss: 0.7293
Epoch 24: val_accuracy did not improve from 0.65625
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 437ms/step - accuracy: 0.7188 - loss: 0.7293 - val_accuracy: 0.5188 - val_loss: 1.0516
Epoch 25/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.6020 - loss: 0.9375
Epoch 25: val_accuracy did not improve from 0.65625
22/22 ━━━━━━━━━━━━━━━━━━━━ 55s 2s/step - accuracy: 0.6025 - loss: 0.9363 - val_accuracy: 0.3313 - val_loss: 2.0324
Epoch 26/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.5625 - loss: 0.8778
Epoch 26: val_accuracy did not improve from 0.65625
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 436ms/step - accuracy: 0.5625 - loss: 0.8778 - val_accuracy: 0.4187 - val_loss: 1.4855
Epoch 27/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.6597 - loss: 0.7980
Epoch 27: val_accuracy did not improve from 0.65625
22/22 ━━━━━━━━━━━━━━━━━━━━ 53s 2s/step - accuracy: 0.6599 - loss: 0.7978 - val_accuracy: 0.4125 - val_loss: 1.9025
Epoch 28/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 10ms/step - accuracy: 0.6250 - loss: 0.7725
Epoch 28: val_accuracy did not improve from 0.65625
22/22 ━━━━━━━━━━━━━━━━━━━━ 10s 471ms/step - accuracy: 0.6250 - loss: 0.7725 - val_accuracy: 0.4000 - val_loss: 1.9279
Epoch 29/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.6815 - loss: 0.7205
Epoch 29: val_accuracy did not improve from 0.65625
22/22 ━━━━━━━━━━━━━━━━━━━━ 53s 2s/step - accuracy: 0.6819 - loss: 0.7200 - val_accuracy: 0.5875 - val_loss: 1.2096
Epoch 30/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.6875 - loss: 0.7389
Epoch 30: val_accuracy did not improve from 0.65625
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 436ms/step - accuracy: 0.6875 - loss: 0.7389 - val_accuracy: 0.6125 - val_loss: 1.0441
Epoch 31/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.6726 - loss: 0.7928
Epoch 31: val_accuracy did not improve from 0.65625
22/22 ━━━━━━━━━━━━━━━━━━━━ 53s 2s/step - accuracy: 0.6708 - loss: 0.7973 - val_accuracy: 0.4125 - val_loss: 1.3055
Epoch 32/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 10ms/step - accuracy: 0.6250 - loss: 0.8743
Epoch 32: val_accuracy did not improve from 0.65625
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 436ms/step - accuracy: 0.6250 - loss: 0.8743 - val_accuracy: 0.5813 - val_loss: 0.9006
Epoch 33/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.7020 - loss: 0.7252
Epoch 33: val_accuracy improved from 0.65625 to 0.68125, saving model to model_checkpoints/baseline_moderate_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 53s 2s/step - accuracy: 0.7026 - loss: 0.7233 - val_accuracy: 0.6812 - val_loss: 1.2498
Epoch 34/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.7812 - loss: 0.4762
Epoch 34: val_accuracy did not improve from 0.68125
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 438ms/step - accuracy: 0.7812 - loss: 0.4762 - val_accuracy: 0.6812 - val_loss: 1.1852
Epoch 35/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.7241 - loss: 0.6112
Epoch 35: val_accuracy did not improve from 0.68125
22/22 ━━━━━━━━━━━━━━━━━━━━ 55s 2s/step - accuracy: 0.7243 - loss: 0.6122 - val_accuracy: 0.5250 - val_loss: 1.8640
Epoch 36/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.7812 - loss: 0.5390
Epoch 36: val_accuracy did not improve from 0.68125
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 425ms/step - accuracy: 0.7812 - loss: 0.5390 - val_accuracy: 0.6313 - val_loss: 1.6129
Epoch 37/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.7759 - loss: 0.5069
Epoch 37: val_accuracy did not improve from 0.68125
22/22 ━━━━━━━━━━━━━━━━━━━━ 52s 2s/step - accuracy: 0.7761 - loss: 0.5068 - val_accuracy: 0.6313 - val_loss: 1.9119
Epoch 38/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.7500 - loss: 0.5000
Epoch 38: val_accuracy did not improve from 0.68125
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 437ms/step - accuracy: 0.7500 - loss: 0.5000 - val_accuracy: 0.6438 - val_loss: 2.2885
Epoch 39/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.7550 - loss: 0.5645
Epoch 39: val_accuracy did not improve from 0.68125
22/22 ━━━━━━━━━━━━━━━━━━━━ 53s 2s/step - accuracy: 0.7554 - loss: 0.5653 - val_accuracy: 0.6438 - val_loss: 1.2677
Epoch 40/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.8438 - loss: 0.4116
Epoch 40: val_accuracy did not improve from 0.68125
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 431ms/step - accuracy: 0.8438 - loss: 0.4116 - val_accuracy: 0.4625 - val_loss: 1.6464
Epoch 41/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.7676 - loss: 0.5290
Epoch 41: val_accuracy did not improve from 0.68125
22/22 ━━━━━━━━━━━━━━━━━━━━ 53s 2s/step - accuracy: 0.7683 - loss: 0.5276 - val_accuracy: 0.6187 - val_loss: 1.9706
Epoch 42/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.6875 - loss: 0.8795
Epoch 42: val_accuracy did not improve from 0.68125
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 439ms/step - accuracy: 0.6875 - loss: 0.8795 - val_accuracy: 0.5750 - val_loss: 1.9356
Epoch 43/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.7735 - loss: 0.5152
Epoch 43: val_accuracy did not improve from 0.68125
22/22 ━━━━━━━━━━━━━━━━━━━━ 53s 2s/step - accuracy: 0.7741 - loss: 0.5140 - val_accuracy: 0.6250 - val_loss: 2.4742
Epoch 43: early stopping
Restoring model weights from the end of the best epoch: 33.
No description has been provided for this image
6/6 ━━━━━━━━━━━━━━━━━━━━ 10s 2s/step - accuracy: 0.6302 - loss: 1.4143
Test accuracy with moderate augmentation: 0.7081

==================================================
Training with aggressive augmentation:
Config: {'rotation_range': 30, 'width_shift_range': 0.3, 'height_shift_range': 0.3, 'zoom_range': 0.3, 'horizontal_flip': True, 'vertical_flip': True, 'brightness_range': [0.7, 1.3], 'shear_range': 0.2, 'fill_mode': 'nearest'}
==================================================
Found 721 images belonging to 8 classes.
Found 160 images belonging to 8 classes.
Found 161 images belonging to 8 classes.
Created augmented generator: aggressive
Epoch 1/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.0818 - loss: 2.1696
Epoch 1: val_accuracy improved from -inf to 0.13750, saving model to model_checkpoints/baseline_aggressive_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 59s 3s/step - accuracy: 0.0825 - loss: 2.1671 - val_accuracy: 0.1375 - val_loss: 2.0692
Epoch 2/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.1875 - loss: 2.0833
Epoch 2: val_accuracy did not improve from 0.13750
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 436ms/step - accuracy: 0.1875 - loss: 2.0833 - val_accuracy: 0.1375 - val_loss: 2.0641
Epoch 3/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.1507 - loss: 2.0285
Epoch 3: val_accuracy improved from 0.13750 to 0.18750, saving model to model_checkpoints/baseline_aggressive_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 56s 3s/step - accuracy: 0.1521 - loss: 2.0262 - val_accuracy: 0.1875 - val_loss: 1.8097
Epoch 4/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.3125 - loss: 1.7392
Epoch 4: val_accuracy improved from 0.18750 to 0.45000, saving model to model_checkpoints/baseline_aggressive_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 10s 475ms/step - accuracy: 0.3125 - loss: 1.7392 - val_accuracy: 0.4500 - val_loss: 1.4313
Epoch 5/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.2801 - loss: 1.6989
Epoch 5: val_accuracy improved from 0.45000 to 0.55000, saving model to model_checkpoints/baseline_aggressive_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 56s 3s/step - accuracy: 0.2817 - loss: 1.6954 - val_accuracy: 0.5500 - val_loss: 1.0215
Epoch 6/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.4688 - loss: 1.3706
Epoch 6: val_accuracy did not improve from 0.55000
22/22 ━━━━━━━━━━━━━━━━━━━━ 12s 573ms/step - accuracy: 0.4688 - loss: 1.3706 - val_accuracy: 0.4187 - val_loss: 1.1648
Epoch 7/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.4584 - loss: 1.2849
Epoch 7: val_accuracy did not improve from 0.55000
22/22 ━━━━━━━━━━━━━━━━━━━━ 52s 2s/step - accuracy: 0.4574 - loss: 1.2858 - val_accuracy: 0.4000 - val_loss: 1.5164
Epoch 8/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.5312 - loss: 1.2273
Epoch 8: val_accuracy did not improve from 0.55000
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 435ms/step - accuracy: 0.5312 - loss: 1.2273 - val_accuracy: 0.3562 - val_loss: 1.7794
Epoch 9/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.4760 - loss: 1.2226
Epoch 9: val_accuracy did not improve from 0.55000
22/22 ━━━━━━━━━━━━━━━━━━━━ 52s 2s/step - accuracy: 0.4769 - loss: 1.2212 - val_accuracy: 0.3938 - val_loss: 1.5520
Epoch 10/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.4375 - loss: 1.2979
Epoch 10: val_accuracy improved from 0.55000 to 0.56875, saving model to model_checkpoints/baseline_aggressive_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 10s 494ms/step - accuracy: 0.4375 - loss: 1.2979 - val_accuracy: 0.5688 - val_loss: 1.1117
Epoch 11/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.5462 - loss: 1.0738
Epoch 11: val_accuracy did not improve from 0.56875
22/22 ━━━━━━━━━━━━━━━━━━━━ 56s 3s/step - accuracy: 0.5455 - loss: 1.0743 - val_accuracy: 0.4000 - val_loss: 1.2565
Epoch 12/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.6875 - loss: 0.8853
Epoch 12: val_accuracy did not improve from 0.56875
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 440ms/step - accuracy: 0.6875 - loss: 0.8853 - val_accuracy: 0.3438 - val_loss: 1.8865
Epoch 13/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.5928 - loss: 0.9961
Epoch 13: val_accuracy did not improve from 0.56875
22/22 ━━━━━━━━━━━━━━━━━━━━ 53s 2s/step - accuracy: 0.5912 - loss: 0.9980 - val_accuracy: 0.5000 - val_loss: 1.5101
Epoch 14/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.5000 - loss: 1.1612
Epoch 14: val_accuracy did not improve from 0.56875
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 439ms/step - accuracy: 0.5000 - loss: 1.1612 - val_accuracy: 0.5000 - val_loss: 1.3775
Epoch 15/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.5532 - loss: 1.0610
Epoch 15: val_accuracy did not improve from 0.56875
22/22 ━━━━━━━━━━━━━━━━━━━━ 53s 2s/step - accuracy: 0.5535 - loss: 1.0606 - val_accuracy: 0.4313 - val_loss: 3.3121
Epoch 16/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.5625 - loss: 1.0291
Epoch 16: val_accuracy did not improve from 0.56875
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 425ms/step - accuracy: 0.5625 - loss: 1.0291 - val_accuracy: 0.3313 - val_loss: 4.0613
Epoch 17/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.5325 - loss: 1.0532
Epoch 17: val_accuracy did not improve from 0.56875
22/22 ━━━━━━━━━━━━━━━━━━━━ 53s 2s/step - accuracy: 0.5332 - loss: 1.0527 - val_accuracy: 0.5063 - val_loss: 1.1263
Epoch 18/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.4688 - loss: 1.0416
Epoch 18: val_accuracy improved from 0.56875 to 0.66250, saving model to model_checkpoints/baseline_aggressive_best.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 11s 507ms/step - accuracy: 0.4688 - loss: 1.0416 - val_accuracy: 0.6625 - val_loss: 0.8198
Epoch 19/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.5818 - loss: 0.9643
Epoch 19: val_accuracy did not improve from 0.66250
22/22 ━━━━━━━━━━━━━━━━━━━━ 56s 3s/step - accuracy: 0.5824 - loss: 0.9642 - val_accuracy: 0.4250 - val_loss: 1.6131
Epoch 20/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 10ms/step - accuracy: 0.5938 - loss: 1.0882
Epoch 20: val_accuracy did not improve from 0.66250
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 437ms/step - accuracy: 0.5938 - loss: 1.0882 - val_accuracy: 0.4500 - val_loss: 1.6330
Epoch 21/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.6719 - loss: 0.8471
Epoch 21: val_accuracy did not improve from 0.66250
22/22 ━━━━━━━━━━━━━━━━━━━━ 53s 2s/step - accuracy: 0.6707 - loss: 0.8481 - val_accuracy: 0.3875 - val_loss: 1.8531
Epoch 22/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.5938 - loss: 1.1434
Epoch 22: val_accuracy did not improve from 0.66250
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 438ms/step - accuracy: 0.5938 - loss: 1.1434 - val_accuracy: 0.4125 - val_loss: 1.6094
Epoch 23/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.6633 - loss: 0.8215
Epoch 23: val_accuracy did not improve from 0.66250
22/22 ━━━━━━━━━━━━━━━━━━━━ 53s 2s/step - accuracy: 0.6643 - loss: 0.8191 - val_accuracy: 0.3375 - val_loss: 3.6371
Epoch 24/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.7188 - loss: 0.9773
Epoch 24: val_accuracy did not improve from 0.66250
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 435ms/step - accuracy: 0.7188 - loss: 0.9773 - val_accuracy: 0.4313 - val_loss: 2.0079
Epoch 25/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.7018 - loss: 0.7451
Epoch 25: val_accuracy did not improve from 0.66250
22/22 ━━━━━━━━━━━━━━━━━━━━ 52s 2s/step - accuracy: 0.7023 - loss: 0.7450 - val_accuracy: 0.3313 - val_loss: 3.5095
Epoch 26/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.6875 - loss: 0.8175
Epoch 26: val_accuracy did not improve from 0.66250
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 421ms/step - accuracy: 0.6875 - loss: 0.8175 - val_accuracy: 0.2937 - val_loss: 3.2652
Epoch 27/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.6867 - loss: 0.8430
Epoch 27: val_accuracy did not improve from 0.66250
22/22 ━━━━━━━━━━━━━━━━━━━━ 53s 2s/step - accuracy: 0.6857 - loss: 0.8442 - val_accuracy: 0.2812 - val_loss: 5.2581
Epoch 28/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.4688 - loss: 1.2304
Epoch 28: val_accuracy did not improve from 0.66250
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 431ms/step - accuracy: 0.4688 - loss: 1.2304 - val_accuracy: 0.3500 - val_loss: 3.3999
Epoch 28: early stopping
Restoring model weights from the end of the best epoch: 18.
No description has been provided for this image
6/6 ━━━━━━━━━━━━━━━━━━━━ 9s 2s/step - accuracy: 0.7233 - loss: 0.7591
Test accuracy with aggressive augmentation: 0.6832


Augmentation Comparison for baseline
Config       Val Acc    Test Acc  
----------------------------------------
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-15-261439e0ec18> in <cell line: 0>()
    206 
    207 # This will be used after determining the best model architecture
--> 208 run_data_augmentation(create_baseline_model, 'baseline', model_results['baseline'])

<ipython-input-15-261439e0ec18> in run_data_augmentation(best_model_func, best_model_name, base_model_results)
    199 
    200     # Then train with each augmentation strategy
--> 201     best_aug, best_aug_acc, aug_results = train_with_augmentations(
    202         best_model_func, best_model_name, base_model_results
    203     )

<ipython-input-15-261439e0ec18> in train_with_augmentations(model_func, model_name, base_model_results)
    133         val_acc = results['val_accuracy']
    134         test_acc = results['test_accuracy']
--> 135         print(f"{aug_name:<12} {val_acc:.4f} {test_acc:.4f}")
    136 
    137     # Find best augmentation

TypeError: unsupported format string passed to NoneType.__format__

Batch Norm¶

In [16]:
def create_improved_model(input_shape, num_classes):
    """
    Improved CNN based on the successful baseline with added batch normalization
    and slight regularization
    """
    model = keras.Sequential([
        # First convolutional block
        layers.Conv2D(32, (3, 3), padding='same', input_shape=input_shape),
        layers.BatchNormalization(),
        layers.Activation('relu'),
        layers.MaxPooling2D((2, 2)),

        # Second convolutional block
        layers.Conv2D(64, (3, 3), padding='same'),
        layers.BatchNormalization(),
        layers.Activation('relu'),
        layers.MaxPooling2D((2, 2)),

        # Third convolutional block
        layers.Conv2D(128, (3, 3), padding='same'),
        layers.BatchNormalization(),
        layers.Activation('relu'),
        layers.MaxPooling2D((2, 2)),

        # Fourth convolutional block
        layers.Conv2D(256, (3, 3), padding='same'),
        layers.BatchNormalization(),
        layers.Activation('relu'),
        layers.MaxPooling2D((2, 2)),

        # Flatten and dense layers
        layers.Flatten(),
        layers.Dense(512),
        layers.BatchNormalization(),
        layers.Activation('relu'),
        layers.Dropout(0.3),  # Add dropout only before final layer
        layers.Dense(num_classes, activation='softmax')
    ])

    # Compile the model
    model.compile(
        optimizer=keras.optimizers.Adam(learning_rate=0.001),
        loss='categorical_crossentropy',
        metrics=['accuracy']
    )

    return model
In [17]:
improved_model = create_improved_model(input_shape=(103,224,3), num_classes=8)
# Train the batch
baseline_history, baseline_test_acc, baseline_y_pred, baseline_y_true = train_model(improved_model, 'improved')
/usr/local/lib/python3.11/dist-packages/keras/src/layers/convolutional/base_conv.py:107: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(activity_regularizer=activity_regularizer, **kwargs)
Epoch 1/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.6332 - loss: 1.2390
Epoch 1: val_accuracy improved from -inf to 0.12500, saving model to model_checkpoints/improved_best.h5
/usr/local/lib/python3.11/dist-packages/keras/src/trainers/epoch_iterator.py:107: UserWarning: Your input ran out of data; interrupting training. Make sure that your dataset or generator can generate at least `steps_per_epoch * epochs` batches. You may need to use the `.repeat()` function when building your dataset.
  self._interrupted_warning()
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 61s 3s/step - accuracy: 0.6400 - loss: 1.2144 - val_accuracy: 0.1250 - val_loss: 6.0096
Epoch 2/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 1.0000 - loss: 0.0370
Epoch 2: val_accuracy did not improve from 0.12500
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 424ms/step - accuracy: 1.0000 - loss: 0.0370 - val_accuracy: 0.1250 - val_loss: 6.2834
Epoch 3/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.9842 - loss: 0.0745
Epoch 3: val_accuracy did not improve from 0.12500
22/22 ━━━━━━━━━━━━━━━━━━━━ 49s 2s/step - accuracy: 0.9842 - loss: 0.0743 - val_accuracy: 0.1250 - val_loss: 9.4697
Epoch 4/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 10ms/step - accuracy: 1.0000 - loss: 0.0249
Epoch 4: val_accuracy did not improve from 0.12500
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 440ms/step - accuracy: 1.0000 - loss: 0.0249 - val_accuracy: 0.1250 - val_loss: 9.5664
Epoch 5/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.9923 - loss: 0.0340
Epoch 5: val_accuracy did not improve from 0.12500
22/22 ━━━━━━━━━━━━━━━━━━━━ 48s 2s/step - accuracy: 0.9923 - loss: 0.0341 - val_accuracy: 0.1250 - val_loss: 12.5794
Epoch 6/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 10ms/step - accuracy: 0.9688 - loss: 0.0308
Epoch 6: val_accuracy did not improve from 0.12500
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 436ms/step - accuracy: 0.9688 - loss: 0.0308 - val_accuracy: 0.1250 - val_loss: 12.6291
Epoch 7/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.9940 - loss: 0.0300
Epoch 7: val_accuracy did not improve from 0.12500
22/22 ━━━━━━━━━━━━━━━━━━━━ 48s 2s/step - accuracy: 0.9941 - loss: 0.0297 - val_accuracy: 0.1250 - val_loss: 14.2568
Epoch 8/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 1.0000 - loss: 0.0115
Epoch 8: val_accuracy did not improve from 0.12500
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 436ms/step - accuracy: 1.0000 - loss: 0.0115 - val_accuracy: 0.1250 - val_loss: 14.3763
Epoch 9/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.9982 - loss: 0.0121
Epoch 9: val_accuracy did not improve from 0.12500
22/22 ━━━━━━━━━━━━━━━━━━━━ 48s 2s/step - accuracy: 0.9982 - loss: 0.0121 - val_accuracy: 0.1250 - val_loss: 15.3026
Epoch 10/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 10ms/step - accuracy: 1.0000 - loss: 0.0039
Epoch 10: val_accuracy did not improve from 0.12500
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 437ms/step - accuracy: 1.0000 - loss: 0.0039 - val_accuracy: 0.1250 - val_loss: 15.3234
Epoch 11/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.9995 - loss: 0.0054
Epoch 11: val_accuracy did not improve from 0.12500
22/22 ━━━━━━━━━━━━━━━━━━━━ 48s 2s/step - accuracy: 0.9995 - loss: 0.0056 - val_accuracy: 0.1250 - val_loss: 15.4392
Epoch 11: early stopping
Restoring model weights from the end of the best epoch: 1.
No description has been provided for this image
6/6 ━━━━━━━━━━━━━━━━━━━━ 10s 2s/step - accuracy: 0.2394 - loss: 4.6008
Test accuracy: 0.1242
Test loss: 6.0312
WARNING:tensorflow:5 out of the last 13 calls to <function TensorFlowTrainer.make_predict_function.<locals>.one_step_on_data_distributed at 0x7e5e2ada05e0> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has reduce_retracing=True option that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for  more details.
6/6 ━━━━━━━━━━━━━━━━━━━━ 10s 1s/step
No description has been provided for this image
Classification Report:
              precision    recall  f1-score   support

       th_10       0.12      1.00      0.22        20
       th_11       0.00      0.00      0.00        20
       th_12       0.00      0.00      0.00        20
       th_13       0.00      0.00      0.00        20
       th_14       0.00      0.00      0.00        20
       th_15       0.00      0.00      0.00        22
       th_16       0.00      0.00      0.00        18
       th_17       0.00      0.00      0.00        21

    accuracy                           0.12       161
   macro avg       0.02      0.12      0.03       161
weighted avg       0.02      0.12      0.03       161

/usr/local/lib/python3.11/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))
/usr/local/lib/python3.11/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))
/usr/local/lib/python3.11/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))

Regularized model¶

In [18]:
def create_regularized_model(input_shape, num_classes):
    """
    Regularized CNN that prevents overfitting on the Clash of Clans dataset
    """
    model = keras.Sequential([
        # First convolutional block
        layers.Conv2D(32, (3, 3), padding='same', kernel_regularizer=keras.regularizers.l2(0.001), input_shape=input_shape),
        layers.BatchNormalization(),
        layers.Activation('relu'),
        layers.MaxPooling2D((2, 2)),
        layers.Dropout(0.1),  # Light dropout after each block

        # Second convolutional block
        layers.Conv2D(64, (3, 3), padding='same', kernel_regularizer=keras.regularizers.l2(0.001)),
        layers.BatchNormalization(),
        layers.Activation('relu'),
        layers.MaxPooling2D((2, 2)),
        layers.Dropout(0.1),

        # Third convolutional block
        layers.Conv2D(128, (3, 3), padding='same', kernel_regularizer=keras.regularizers.l2(0.001)),
        layers.BatchNormalization(),
        layers.Activation('relu'),
        layers.MaxPooling2D((2, 2)),
        layers.Dropout(0.1),

        # Fourth convolutional block
        layers.Conv2D(256, (3, 3), padding='same', kernel_regularizer=keras.regularizers.l2(0.001)),
        layers.BatchNormalization(),
        layers.Activation('relu'),
        layers.MaxPooling2D((2, 2)),
        layers.Dropout(0.1),

        # Flatten and dense layers
        layers.Flatten(),
        layers.Dense(512, kernel_regularizer=keras.regularizers.l2(0.001)),
        layers.BatchNormalization(),
        layers.Activation('relu'),
        layers.Dropout(0.5),  # Heavy dropout before final classification
        layers.Dense(num_classes, activation='softmax')
    ])

    # Compile the model with a lower learning rate
    model.compile(
        optimizer=keras.optimizers.Adam(learning_rate=0.0005),  # Reduced learning rate
        loss='categorical_crossentropy',
        metrics=['accuracy']
    )

    return model
In [19]:
reg_model = create_regularized_model(input_shape=(103,224,3), num_classes=8)
# Train the batch
baseline_history, baseline_test_acc, baseline_y_pred, baseline_y_true = train_model(reg_model, 'regularized')
/usr/local/lib/python3.11/dist-packages/keras/src/layers/convolutional/base_conv.py:107: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(activity_regularizer=activity_regularizer, **kwargs)
Epoch 1/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.5151 - loss: 2.7625
Epoch 1: val_accuracy improved from -inf to 0.12500, saving model to model_checkpoints/regularized_best.h5
/usr/local/lib/python3.11/dist-packages/keras/src/trainers/epoch_iterator.py:107: UserWarning: Your input ran out of data; interrupting training. Make sure that your dataset or generator can generate at least `steps_per_epoch * epochs` batches. You may need to use the `.repeat()` function when building your dataset.
  self._interrupted_warning()
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 

22/22 ━━━━━━━━━━━━━━━━━━━━ 64s 2s/step - accuracy: 0.5212 - loss: 2.7444 - val_accuracy: 0.1250 - val_loss: 7.3734
Epoch 2/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.8438 - loss: 1.8718
Epoch 2: val_accuracy did not improve from 0.12500
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 445ms/step - accuracy: 0.8438 - loss: 1.8718 - val_accuracy: 0.1250 - val_loss: 7.6905
Epoch 3/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.9079 - loss: 1.6603
Epoch 3: val_accuracy did not improve from 0.12500
22/22 ━━━━━━━━━━━━━━━━━━━━ 49s 2s/step - accuracy: 0.9086 - loss: 1.6590 - val_accuracy: 0.1250 - val_loss: 12.3953
Epoch 4/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 10ms/step - accuracy: 0.9062 - loss: 1.5929
Epoch 4: val_accuracy did not improve from 0.12500
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 444ms/step - accuracy: 0.9062 - loss: 1.5929 - val_accuracy: 0.1250 - val_loss: 12.6197
Epoch 5/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.9621 - loss: 1.4977
Epoch 5: val_accuracy did not improve from 0.12500
22/22 ━━━━━━━━━━━━━━━━━━━━ 49s 2s/step - accuracy: 0.9621 - loss: 1.4969 - val_accuracy: 0.1250 - val_loss: 16.1690
Epoch 6/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 10ms/step - accuracy: 1.0000 - loss: 1.3963
Epoch 6: val_accuracy did not improve from 0.12500
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 444ms/step - accuracy: 1.0000 - loss: 1.3963 - val_accuracy: 0.1250 - val_loss: 16.2906
Epoch 7/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.9644 - loss: 1.4607
Epoch 7: val_accuracy did not improve from 0.12500
22/22 ━━━━━━━━━━━━━━━━━━━━ 49s 2s/step - accuracy: 0.9644 - loss: 1.4599 - val_accuracy: 0.1250 - val_loss: 18.8316
Epoch 8/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 10ms/step - accuracy: 1.0000 - loss: 1.3708
Epoch 8: val_accuracy did not improve from 0.12500
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 444ms/step - accuracy: 1.0000 - loss: 1.3708 - val_accuracy: 0.1250 - val_loss: 18.9336
Epoch 9/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.9804 - loss: 1.3939
Epoch 9: val_accuracy did not improve from 0.12500
22/22 ━━━━━━━━━━━━━━━━━━━━ 49s 2s/step - accuracy: 0.9806 - loss: 1.3929 - val_accuracy: 0.1250 - val_loss: 20.9041
Epoch 10/50
 1/22 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 1.0000 - loss: 1.3040
Epoch 10: val_accuracy did not improve from 0.12500
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 448ms/step - accuracy: 1.0000 - loss: 1.3040 - val_accuracy: 0.1250 - val_loss: 20.9581
Epoch 11/50
22/22 ━━━━━━━━━━━━━━━━━━━━ 0s 2s/step - accuracy: 0.9969 - loss: 1.3182
Epoch 11: val_accuracy did not improve from 0.12500
22/22 ━━━━━━━━━━━━━━━━━━━━ 49s 2s/step - accuracy: 0.9968 - loss: 1.3178 - val_accuracy: 0.1250 - val_loss: 22.0826
Epoch 11: early stopping
Restoring model weights from the end of the best epoch: 1.
No description has been provided for this image
6/6 ━━━━━━━━━━━━━━━━━━━━ 10s 2s/step - accuracy: 0.2394 - loss: 6.4660
Test accuracy: 0.1242
Test loss: 7.3939
WARNING:tensorflow:5 out of the last 13 calls to <function TensorFlowTrainer.make_predict_function.<locals>.one_step_on_data_distributed at 0x7e5dbc5093a0> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has reduce_retracing=True option that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for  more details.
6/6 ━━━━━━━━━━━━━━━━━━━━ 10s 1s/step
No description has been provided for this image
Classification Report:
              precision    recall  f1-score   support

       th_10       0.12      1.00      0.22        20
       th_11       0.00      0.00      0.00        20
       th_12       0.00      0.00      0.00        20
       th_13       0.00      0.00      0.00        20
       th_14       0.00      0.00      0.00        20
       th_15       0.00      0.00      0.00        22
       th_16       0.00      0.00      0.00        18
       th_17       0.00      0.00      0.00        21

    accuracy                           0.12       161
   macro avg       0.02      0.12      0.03       161
weighted avg       0.02      0.12      0.03       161

/usr/local/lib/python3.11/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))
/usr/local/lib/python3.11/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))
/usr/local/lib/python3.11/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))

Stop and check data says Mr Claude due to massive overfitting with batch norm¶

In [20]:
# Check class distribution in train and validation sets
def analyze_data_split(train_ds, val_ds, class_names):
    train_counts = {class_name: 0 for class_name in class_names}
    val_counts = {class_name: 0 for class_name in class_names}

    # Count samples in training set
    for images, labels in train_ds:
        for label in labels:
            label_idx = tf.argmax(label).numpy()
            train_counts[class_names[label_idx]] += 1

    # Count samples in validation set
    for images, labels in val_ds:
        for label in labels:
            label_idx = tf.argmax(label).numpy()
            val_counts[class_names[label_idx]] += 1

    # Print results
    print("Class distribution in training set:")
    for class_name, count in train_counts.items():
        print(f"{class_name}: {count}")

    print("\nClass distribution in validation set:")
    for class_name, count in val_counts.items():
        print(f"{class_name}: {count}")

    return train_counts, val_counts

Transfer Model¶

In [23]:
# Define the transfer learning model
def create_transfer_model(input_shape, num_classes):
    """
    Transfer learning model using MobileNetV2 that respects the dataset's dimensions
    """
    # Create the base model
    base_model = keras.applications.MobileNetV2(
        input_shape=input_shape,
        include_top=False,
        weights='imagenet'
    )

    # Freeze the base model
    base_model.trainable = False

    # Create model with lightweight head
    model = keras.Sequential([
        # Preprocessing layer specifically for MobileNetV2
        keras.layers.Rescaling(1./127.5, offset=-1, input_shape=input_shape),

        # Base model
        base_model,

        # Classification head with regularization
        layers.GlobalAveragePooling2D(),
        layers.Dense(128, activation=None),
        layers.BatchNormalization(),
        layers.Activation('relu'),
        layers.Dropout(0.3),
        layers.Dense(num_classes, activation='softmax')
    ])

    # Compile model with a conservative learning rate
    model.compile(
        optimizer=keras.optimizers.Adam(learning_rate=0.0005),
        loss='categorical_crossentropy',
        metrics=['accuracy']
    )

    return model

# Training function
def train_transfer_model():
    # Create the model
    input_shape = (IMG_HEIGHT, IMG_WIDTH, 3)
    transfer_model = create_transfer_model(input_shape, num_classes)

    # Print model summary
    transfer_model.summary()

    # Define callbacks
    early_stopping = keras.callbacks.EarlyStopping(
        monitor='val_loss',
        patience=5,
        restore_best_weights=True
    )

    # Train the model
    history = transfer_model.fit(
        train_generator,
        steps_per_epoch=steps_per_epoch,
        epochs=30,
        validation_data=validation_generator,
        validation_steps=validation_steps,
        callbacks=[early_stopping]
    )

    # Evaluate the model
    print("\nEvaluating on validation data:")
    val_loss, val_acc = transfer_model.evaluate(validation_generator)
    print(f"Validation accuracy: {val_acc:.4f}")

    print("\nEvaluating on test data:")
    test_loss, test_acc = transfer_model.evaluate(test_generator)
    print(f"Test accuracy: {test_acc:.4f}")

    return transfer_model, history

# Call the function to train and evaluate the transfer learning model
transfer_model, history = train_transfer_model()
<ipython-input-23-725c173cb29d>:7: UserWarning: `input_shape` is undefined or non-square, or `rows` is not in [96, 128, 160, 192, 224]. Weights for input shape (224, 224) will be loaded as the default.
  base_model = keras.applications.MobileNetV2(
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/mobilenet_v2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_224_no_top.h5
9406464/9406464 ━━━━━━━━━━━━━━━━━━━━ 0s 0us/step
/usr/local/lib/python3.11/dist-packages/keras/src/layers/preprocessing/tf_data_layer.py:19: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(**kwargs)
Model: "sequential_8"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ rescaling (Rescaling)           │ (None, 103, 224, 3)    │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ mobilenetv2_1.00_224            │ (None, 4, 7, 1280)     │     2,257,984 │
│ (Functional)                    │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ global_average_pooling2d        │ (None, 1280)           │             0 │
│ (GlobalAveragePooling2D)        │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_16 (Dense)                │ (None, 128)            │       163,968 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization_10          │ (None, 128)            │           512 │
│ (BatchNormalization)            │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ activation_10 (Activation)      │ (None, 128)            │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout_6 (Dropout)             │ (None, 128)            │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_17 (Dense)                │ (None, 8)              │         1,032 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
 Total params: 2,423,496 (9.24 MB)
 Trainable params: 165,256 (645.53 KB)
 Non-trainable params: 2,258,240 (8.61 MB)
Epoch 1/30
22/22 ━━━━━━━━━━━━━━━━━━━━ 74s 3s/step - accuracy: 0.2754 - loss: 1.8546 - val_accuracy: 0.1500 - val_loss: 2.3549
Epoch 2/30
/usr/local/lib/python3.11/dist-packages/keras/src/trainers/epoch_iterator.py:107: UserWarning: Your input ran out of data; interrupting training. Make sure that your dataset or generator can generate at least `steps_per_epoch * epochs` batches. You may need to use the `.repeat()` function when building your dataset.
  self._interrupted_warning()
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 432ms/step - accuracy: 0.7188 - loss: 1.0714 - val_accuracy: 0.1250 - val_loss: 2.3567
Epoch 3/30
22/22 ━━━━━━━━━━━━━━━━━━━━ 50s 2s/step - accuracy: 0.6528 - loss: 1.0991 - val_accuracy: 0.1250 - val_loss: 2.4446
Epoch 4/30
22/22 ━━━━━━━━━━━━━━━━━━━━ 10s 452ms/step - accuracy: 0.5882 - loss: 0.9606 - val_accuracy: 0.1250 - val_loss: 2.4497
Epoch 5/30
22/22 ━━━━━━━━━━━━━━━━━━━━ 50s 2s/step - accuracy: 0.7147 - loss: 0.8884 - val_accuracy: 0.1250 - val_loss: 2.4683
Epoch 6/30
22/22 ━━━━━━━━━━━━━━━━━━━━ 9s 439ms/step - accuracy: 0.7812 - loss: 0.7573 - val_accuracy: 0.1250 - val_loss: 2.4654

Evaluating on validation data:
5/5 ━━━━━━━━━━━━━━━━━━━━ 9s 2s/step - accuracy: 0.1090 - loss: 2.6852
Validation accuracy: 0.1500

Evaluating on test data:
6/6 ━━━━━━━━━━━━━━━━━━━━ 14s 2s/step - accuracy: 0.1108 - loss: 2.6382
Test accuracy: 0.1429

Claude losing context¶

  • at this point claude has lost memory that we've already checked data to start
  • it forgets that it was claude who determined the input size and in chat has now questioned why the input size was done this way. I reminded it that it did this
In [24]:
def visualize_dataset_samples():
    """
    Visualize sample images from training and validation sets to spot potential issues
    """
    # Reset generators to ensure we get fresh batches
    train_generator.reset()
    validation_generator.reset()

    # Get batches
    train_batch = next(train_generator)
    val_batch = next(validation_generator)

    # Extract images and labels
    train_images, train_labels = train_batch
    val_images, val_labels = val_batch

    # Display images
    plt.figure(figsize=(10, 8))

    for i in range(min(4, len(train_images))):
        plt.subplot(2, 4, i+1)
        plt.imshow(train_images[i])
        class_idx = np.argmax(train_labels[i])
        plt.title(f"Train: {class_names[class_idx]}")
        plt.axis('off')

    for i in range(min(4, len(val_images))):
        plt.subplot(2, 4, i+5)
        plt.imshow(val_images[i])
        class_idx = np.argmax(val_labels[i])
        plt.title(f"Val: {class_names[class_idx]}")
        plt.axis('off')

    plt.tight_layout()
    plt.show()

# Run the visualization
visualize_dataset_samples()
No description has been provided for this image
In [26]:
# Re-evaluate the baseline model on validation data
def evaluate_baseline():
    # Recreate the baseline model
    input_shape = (IMG_HEIGHT, IMG_WIDTH, 3)
    baseline_model = create_baseline_model(input_shape, num_classes)

    # Load weights if available, or train from scratch if needed
    try:
        baseline_model.load_weights('./model_checkpoints/baseline_best.h5')
        print("Loaded saved baseline model weights")
    except:
        print("No saved weights found, training baseline model from scratch")
        # Define callbacks
        early_stopping = keras.callbacks.EarlyStopping(
            monitor='val_loss',
            patience=5,
            restore_best_weights=True
        )

        # Train the model
        baseline_model.fit(
            train_generator,
            steps_per_epoch=steps_per_epoch,
            epochs=30,
            validation_data=validation_generator,
            validation_steps=validation_steps,
            callbacks=[early_stopping]
        )

    # Evaluate
    print("\nEvaluating baseline on validation data:")
    val_loss, val_acc = baseline_model.evaluate(validation_generator)
    print(f"Validation accuracy: {val_acc:.4f}")

    print("\nEvaluating baseline on test data:")
    test_loss, test_acc = baseline_model.evaluate(test_generator)
    print(f"Test accuracy: {test_acc:.4f}")

    return baseline_model

# Run baseline evaluation
baseline_model = evaluate_baseline()
Loaded saved baseline model weights

Evaluating baseline on validation data:
5/5 ━━━━━━━━━━━━━━━━━━━━ 10s 2s/step - accuracy: 0.9872 - loss: 0.1015
Validation accuracy: 0.9875

Evaluating baseline on test data:
6/6 ━━━━━━━━━━━━━━━━━━━━ 9s 2s/step - accuracy: 0.9872 - loss: 0.0500
Test accuracy: 0.9814
In [ ]: